Tibbles

Task 1:Loading the tidyverse package.

library(tidyverse)

Task 2:Converting the iris dataset to a tibble.

as_tibble(iris)

Task 3: Creating a tibble with columns “x,” “y,” and “z,” where “x” ranges from 1 to 5, “y” is 1 for all rows, and “z” is calculated as the square of “x” plus “y” for each row.

tibble(
  x = 1:5, 
  y = 1, 
  z = x ^ 2 + y
)

Task 4:Creating a tibble with columns named “:)” (representing “smile”), ” ” (representing “space”), and “2000” (representing “number”).

tb <- tibble(
  `:)` = "smile", 
  ` ` = "space",
  `2000` = "number"
)
tb

Task 5:Creating a tibble with columns “x,” “y,” and “z,” containing the values “a,” 2, 3.6 and “b,” 1, 8.5 respectively.

tribble(
  ~x, ~y, ~z,
  
  "a", 2, 3.6,
  "b", 1, 8.5
)

Tibbles vs. data.frame

Task-1:Creating a tibble with columns “a,” “b,” “c,” “d,” and “e,” containing 1000 randomly generated values for each column, representing dates, numbers, and letters.

tibble(
  a = lubridate::now() + runif(1e3) * 86400,
  b = lubridate::today() + runif(1e3) * 30,
  c = 1:1e3,
  d = runif(1e3),
  e = sample(letters, 1e3, replace = TRUE)
)

Task 2: Tnstalling the package

package_to_install <- c("nycflights13")

for (package_name in package_to_install) {
  if (!requireNamespace(package_name, quietly = TRUE)) {
    install.packages(package_name)
  }
}
library(nycflights13)

Task 3: Printing the first 10 rows of the nycflights13::flights dataset with unlimited width.

nycflights13::flights %>% 
  print(n = 10, width = Inf)

Task 4: Viewing the nycflights13::flights dataset in a separate window for interactive exploration.

nycflights13::flights %>% 
  View()

Subsetting

Task 1: Creating a tibble named “df” with columns “x” and “y,” then accessing the “x” column using different methods:

df <- tibble(
  x = runif(5),#function that generates random numbers from a uniform distribution
  y = rnorm(5) # function that generates random numbers from a normal (Gaussian) distribution
)

df$x
[1] 0.4134781 0.3841133 0.5761670 0.6047906 0.8490257
df[["x"]]
[1] 0.4134781 0.3841133 0.5761670 0.6047906 0.8490257
df[[1]]
[1] 0.4134781 0.3841133 0.5761670 0.6047906 0.8490257
df %>% .$x
[1] 0.4134781 0.3841133 0.5761670 0.6047906 0.8490257

Interacting with older code

Task-1: Determining the class of the object “tb” after converting it to a data frame.

class(as.data.frame(tb))
[1] "data.frame"

Exercises

Task-1: How can you tell if an object is a tibble? (Hint: try printing mtcars, which is a regular data frame).

mtcars

Task-2

# In a data.frame, extracting a non-existent column returns NULL,
# whereas in a tibble, it raises an error, providing immediate feedback.
# Other operations, such as extracting existing columns and subsets of columns,
# behave similarly across both data frames and tibbles.
# The default behavior of data.frames may lead to frustration
# due to the lack of error feedback for non-existent columns,
# potentially causing unnoticed mistakes and difficulty in debugging.
# In contrast, tibbles offer more robust behavior, enhancing data integrity
# and debugging efficiency.

df <- data.frame(abc = 1, xyz = "a")

# Extracting non-existent column in a data.frame
df$x  # Returns NULL
[1] "a"
# Extracting existing column in a data.frame
df[, "xyz"]  # Returns a data frame with one column containing the values of the "xyz" column
[1] "a"
# Extracting multiple columns in a data.frame
df[, c("abc", "xyz")]  # Returns a data frame containing only the specified columns
NA

Task-3:If you have the name of a variable stored in an object, e.g. var <- “mpg”, how can you extract the reference variable from a tibble?

No pacakages

# heights <- read_csv("data/heights.csv")

Task 1: listing several tables: table1, table2, table3, table4a, and table4b.

table1
table2
table3
table4a
table4b

Task 2: Calculating the rate by dividing the number of cases by the population and then multiplying by 10,000 for table1.

table1 %>% 
  mutate(rate = cases / population * 10000)

Task 3: Counting the occurrences of each year in table1, using the ‘cases’ column as the weight.

table1 %>% 
  count(year, wt = cases)

Task 4: Creating a ggplot using table1, plotting ‘year’ against ‘cases’ with lines grouped by ‘country’ and colored in grey50, along with points colored by ‘country’.

library(ggplot2)
ggplot(table1, aes(year, cases)) + 
  geom_line(aes(group = country), colour = "grey50") + 
  geom_point(aes(colour = country))

Pivoting

Longer

Task-1: referring to ‘table4a’

table4a

Task-2: Reshaping table4a using pivot_longer for columns ‘1999’ and ‘2000’ into ‘year’ and ‘cases’.

table4a %>% 
  pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "cases")

Task-3: Reshaping table4b with pivot_longer for columns ‘1999’ and ‘2000’ into ‘year’ and ‘population’.

table4b %>% 
  pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "population")  #function transforms wide data into long format by stacking multiple columns into two: one for variable names and one for their corresponding values

Task-4: creating tidy datasets tidy4a and tidy4b by using pivot_longer on table4a and table4b to reshape them. Then, performing a left join on tidy4a and tidy4b.

tidy4a <- table4a %>% 
  pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "cases")
tidy4b <- table4b %>% 
  pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "population")
left_join(tidy4a, tidy4b)
Joining with `by = join_by(country, year)`

Wider

Task-1:Displaying table 2

table2

Task-2: using the pivot_wider function on table2 to transform it from long to wide format, with ‘type’ becoming the new column names and ‘count’ being the corresponding values.

table2 %>%
    pivot_wider(names_from = type, values_from = count)

Separating and uniting

Separate

Task-1:displaying table3

 table3

Task-2: Using the separate function on table3 splits the ‘rate’ column into two separate columns named ‘cases’ and ‘population’.

table3 %>% 
  separate(rate, into = c("cases", "population"))

Task-3:Using the separate function on table3 splits the ‘rate’ column into two separate columns named ‘cases’ and ‘population’, using the ‘/’ character as the separator.

table3 %>% 
  separate(rate, into = c("cases", "population"), sep = "/")

Task-4:Using the separate function on table3 splits the ‘rate’ column into two separate columns named ‘cases’ and ‘population’, converting the resulting columns to their appropriate data types.

table3 %>% 
  separate(rate, into = c("cases", "population"), convert = TRUE)

Task-5: Applying the separate function to table3, the ‘year’ column is divided into two separate columns labeled ‘century’ and ‘year’, with the separator defined as the second character.

table3 %>% 
  separate(year, into = c("century", "year"), sep = 2)

Unite

Task-1: The unite function is applied to table5 to merge the ‘century’ and ‘year’ columns into a single column named ‘new’.

table5 %>% 
  unite(new, century, year)

Task-2: unite function is applied to table5 to merge the ‘century’ and ‘year’ columns into a single column named ‘new’, with no separator between them.

table5 %>% 
  unite(new, century, year, sep = "")

Missing values

Task-1: Create a tibble named “stocks” with columns “year”, “qtr” (quarter), and “return”, having data for 2015 and 2016, with quarterly returns specified and some missing entries as NA.

stocks <- tibble(
  year   = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
  qtr    = c(   1,    2,    3,    4,    2,    3,    4),
  return = c(1.88, 0.59, 0.35,   NA, 0.92, 0.17, 2.66)
)

Task-2:Pivoting the “stocks” tibble to widen the data, extracting columns from the “year” variable and values from the “return” variable.

stocks %>% 
  pivot_wider(names_from = year, values_from = return)

Task-3: pivot the data to a wide format with columns for each year’s returns, then reshape it back to a long format, keeping only the non-missing values in the “return” column.

stocks %>% 
  pivot_wider(names_from = year, values_from = return) %>% 
  pivot_longer(
    cols = c(`2015`, `2016`), 
    names_to = "year", 
    values_to = "return", 
    values_drop_na = TRUE
  )

Task-4:Filling missing combinations of “year” and “qtr” in the “stocks” dataset.

stocks %>% 
  complete(year, qtr)

Task-5:Creating a tibble named “treatment” containing information about individuals, their treatment groups, and their responses, with some missing values for the “person” column.

treatment <- tribble(
  ~ person,           ~ treatment, ~response,
  "Derrick Whitmore", 1,           7,
  NA,                 2,           10,
  NA,                 3,           9,
  "Katherine Burke",  1,           4
)

Task-6: Filling the missing values in the “person” column of the “treatment” tibble.

treatment %>% 
  fill(person)
NA

Case Study

Task-1: Loading data set

who

Task-2:Pivoting the “who” dataset from wide to long format, condensing columns into “cases” and capturing the original column names in “key”.

who1 <- who %>% 
  pivot_longer(
    cols = new_sp_m014:newrel_f65, 
    names_to = "key", 
    values_to = "cases", 
    values_drop_na = TRUE
  )
who1

Task-3:Counting the occurrences of each “key” in the “who1” dataset.

  who1 %>% 
    count(key)

Task-4:Replacing “newrel” with “new_rel” in the “key” column of the “who1” dataset to create “who2.”

who2 <- who1 %>% 
  mutate(key = stringr::str_replace(key, "newrel", "new_rel"))
who2

Task-5:Separating the “key” column in the “who2” dataset into “new,” “type,” and “sexage” columns using “_” as the separator to create “who3.”

who3 <- who2 %>% 
  separate(key, c("new", "type", "sexage"), sep = "_")
who3

Task-6:Counting the occurrences of each unique value in the “new” column of the “who3” dataset.

who3 %>% 
  count(new)

Task-7:Removing the “new”, “iso2”, and “iso3” columns from the “who3” dataset and assigning the result to “who4”.

who4 <- who3 %>% 
  select(-new, -iso2, -iso3)

Task-8:Splitting the “sexage” column of the “who4” dataset into “sex” and “age” columns, separated by the first character, and assigning the result to “who5”.

who5 <- who4 %>% 
  separate(sexage, c("sex", "age"), sep = 1)
who5

Task-9:Transforming the “who” dataset from wide to long format, adjusting column names, extracting meaningful variables, dropping unnecessary columns, and splitting the “sexage” column into “sex” and “age”.

who %>%
  pivot_longer(
    cols = new_sp_m014:newrel_f65, 
    names_to = "key", 
    values_to = "cases", 
    values_drop_na = TRUE
  ) %>% 
  mutate(
    key = stringr::str_replace(key, "newrel", "new_rel")
  ) %>%
  separate(key, c("new", "var", "sexage")) %>% 
  select(-new, -iso2, -iso3) %>% 
  separate(sexage, c("sex", "age"), sep = 1)
NA

CH-13: Relational data

Task-1:Loding the libraries

library(tidyverse)
library(nycflights13)

nycflights13

Task-1: airlines data

airlines

Task-2: airports data

airports

Task-3: planes data

planes 

Task-4: weather data

weather 

Keys

Task-1Counting the occurrences of each tail number in the “planes” table and filtering for those with more than one occurrence.

planes %>% 
  count(tailnum) %>% 
  filter(n > 1)

Task-2:Counting the occurrences of each combination of year, month, day, hour, and origin in the “weather” table and filtering for those with more than one occurrence.

weather %>% 
  count(year, month, day, hour, origin) %>% 
  filter(n > 1)

Task-3:Counting the occurrences of each combination of year, month, day, and flight in the “flights” table and filtering for those with more than one occurrence.

flights %>% 
  count(year, month, day, flight) %>% 
  filter(n > 1)

Task-4:Counting the occurrences of each combination of year, month, day, and tail number in the “flights” table and filtering for those with more than one occurrence.

flights %>% 
  count(year, month, day, tailnum) %>% 
  filter(n > 1)

Mutating joins

Task-1: Creating a subset of the “flights” table named “flights2” containing columns from “year” to “day”, “hour”, “origin”, “dest”, “tailnum”, and “carrier”.

flights2 <- flights %>% 
  select(year:day, hour, origin, dest, tailnum, carrier)
flights2

Task-2:Removing the “origin” and “dest” columns from “flights2” table and then performing a left join with the “airlines” table, using the “carrier” column as the key for matching.

flights2 %>%
  select(-origin, -dest) %>% 
  left_join(airlines, by = "carrier")

Task-3:Shortening the command by removing “selecting” and directly “mutating” the “name” column with the corresponding airline names from the “airlines” table based on the “carrier” column.

flights2 %>%
  select(-origin, -dest) %>% 
  mutate(name = airlines$name[match(carrier, airlines$carrier)])

Understanding joins

Task-1:Creating two tibbles, “x” and “y”, each with a “key” column and an associated “val_x” or “val_y” column, respectively.

x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     3, "x3"
)
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2",
     4, "y3"
)

x
y

Inner join

Task-1:Joining tibbles x and y using an inner join operation based on the “key” column.

x %>% 
  inner_join(y, by = "key")

Duplicate keys

Task-1: Joining tibble x with tibble y using the common column “key”.

x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     2, "x3",
     1, "x4"
)
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2"
)

Task-2:Performing a left join between tibble x and tibble y based on the common column “key”.

left_join(x, y, by = "key")

Task-3:Creating two tibbles, x and y, with columns “key”, “val_x”, and “val_y”, populated with corresponding values.

x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     2, "x3",
     3, "x4"
)
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2",
     2, "y3",
     3, "y4"
)

Task-4:Performing a left join on tibbles x and y using the “key” column as the join key.

left_join(x, y, by = "key")
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.

Defining the key columns

Task-1:Performing a left join between the flights2 tibble and the weather tibble.

flights2 %>% 
  left_join(weather)
Joining with `by = join_by(year, month, day, hour, origin)`

Task-2:Performing a left join between the flights2 tibble and the planes tibble using the “tailnum” column as the key.

flights2 %>% 
  left_join(planes, by = "tailnum")

Task-3:Performing a left join between the flights2 tibble and the airports tibble, matching the “dest” column from flights2 with the “faa” column from airports.

flights2 %>% 
  left_join(airports, c("dest" = "faa"))

Task-4:Performing a left join between the flights2 tibble and the airports tibble, matching the “origin” column from flights2 with the “faa” column from airports.

flights2 %>% 
  left_join(airports, c("origin" = "faa"))

Filtering joins

Task-1: Calculating the top 10 destinations by counting the occurrences in the “dest” column of the flights tibble, sorted in descending order, and then displaying the result.

top_dest <- flights %>%
  count(dest, sort = TRUE) %>%
  head(10)
top_dest

Task-2: Filtering the flights tibble to include only rows where the destination (dest) matches any of the top 10 destinations identified in the previous step.

flights %>% 
  filter(dest %in% top_dest$dest)
#%in% operator in R is used to check if elements in one vector are present in another vector

Task-3: Selecting rows from the flights dataset where the destination airport matches one of the top 10 destinations previously identified.

flights %>% 
  semi_join(top_dest)
Joining with `by = join_by(dest)`

Task-4: Filtering out flights with tail numbers present in the planes dataset and counting the occurrences of each unique tail number, sorting the result.

flights %>%
  anti_join(planes, by = "tailnum") %>%
  count(tailnum, sort = TRUE)

Set operations

Task-1:creating two tibbles, df1 and df2, each with columns x and y, containing sample data.

df1 <- tribble(
  ~x, ~y,
   1,  1,
   2,  1
)
df2 <- tribble(
  ~x, ~y,
   1,  1,
   1,  2
)

Task-2:performing set operations on the tibbles df1 and df2, including intersection, union, and set differences.

intersect(df1, df2)
union(df1, df2)
setdiff(df1, df2)
setdiff(df2, df1)

CH-14: Strings

Basic Info:string1 <- “This is a string” string2 <- ‘If I want to include a “quote” inside a string, I use single quotes’

Task-1:To include a literal single or double quote in a string you can use  to “escape” it

double_quote <- "\"" # or '"'
single_quote <- '\'' # or "'"

Task-2: Understanding the character


x <- c("\"", "\\") #backslash is escape character
x
[1] "\"" "\\"
writeLines(x)
"
\

String length

Task-1:

str_length(c("a", "R for data science", NA))
[1]  1 18 NA

Combining strings

Task-1:Combining the strings

str_c("x", "y")
[1] "xy"
str_c("x", "y", "z")
[1] "xyz"

Task-2:Using the sep argument to control how they’re separated.

str_c("x", "y", sep = ", ")
[1] "x, y"

Task-3:Performing concatenation with “|” and “-” at both ends of each element of vector x, and replacing NA values with empty strings before concatenation.

x <- c("abc", NA)
str_c("|-", x, "-|")
[1] "|-abc-|" NA       
str_c("|-", str_replace_na(x), "-|")
[1] "|-abc-|" "|-NA-|" 

Task-4: concatenating each element of the vector c(“a”, “b”, “c”) with a prefix “prefix-” and a suffix “-suffix”.

str_c("prefix-", c("a", "b", "c"), "-suffix")
[1] "prefix-a-suffix" "prefix-b-suffix" "prefix-c-suffix"

Task-5: combining strings

name <- "Hadley"
time_of_day <- "morning"
birthday <- FALSE

str_c(
  "Good ", time_of_day, " ", name,
  if (birthday) " and HAPPY BIRTHDAY",
  "."
)
[1] "Good morning Hadley."

Subsetting strings

Task-1:Extracting the first three characters from each element in the vector x using str_sub.

x <- c("Apple", "Banana", "Pear")
str_sub(x, 1, 3)
[1] "App" "Ban" "Pea"

Task-2:negative numbers count backwards from end

str_sub(x, -3, -1)
[1] "ple" "ana" "ear"

Task-3:using the assignment form of str_sub() to modify strings

str_sub(x, 1, 1) <- str_to_lower(str_sub(x, 1, 1))
x
[1] "apple"  "banana" "pear"  

Locales

Task-1:Changing the case

str_to_upper(c("i", "ı"))
[1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
[1] "İ" "I"

Task-2:Sorting the character vector x alphabetically using the English (en) locale and the Hawaiian (haw) locale.

x <- c("apple", "eggplant", "banana")
str_sort(x, locale = "en") 
[1] "apple"    "banana"   "eggplant"
str_sort(x, locale = "haw") 
[1] "apple"    "eggplant" "banana"  

Matching patterns with regular expressions

Basic matches

Task-1:Searching for the pattern “an” within each element of x and displaying the matches.

x <- c("apple", "banana", "pear")
str_view(x, "an")
[2] │ b<an><an>a

Task-2:Displaying elements in x where any character is followed by “a” and then any character.

str_view(x, ".a.")
[2] │ <ban>ana
[3] │ p<ear>

Task-3

# To create the regular expression, we need \\
dot <- "\\."

# But the expression itself only contains one:
writeLines(dot)
\.
# And this tells R to look for an explicit .
str_view(c("abc", "a.c", "bef"), "a\\.c")
[2] │ <a.c>

Task-4: Displaying elements in x where the sequence “\” occurs.

x <- "a\\b"
writeLines(x)
a\b
str_view(x, "\\\\")
[1] │ a<\>b

Anchors

Task-1: Displaying elements in x that start with “a” and end with “a” respectively.

x <- c("apple", "banana", "pear")
str_view(x, "^a")
[1] │ <a>pple
str_view(x, "a$")
[2] │ banan<a>

Task-2: Highlighting “apple” occurrences in x and instances where it’s the only content.

x <- c("apple pie", "apple", "apple cake")
str_view(x, "apple")
[1] │ <apple> pie
[2] │ <apple>
[3] │ <apple> cake
str_view(x, "^apple$")
[2] │ <apple>

Character classes and alternatives

Task-1: Visualizing patterns matching “a.c”, “a*c”, and “a c” in the provided character vector.

str_view(c("abc", "a.c", "a*c", "a c"), "a[.]c")
[2] │ <a.c>
str_view(c("abc", "a.c", "a*c", "a c"), ".[*]c")
[3] │ <a*c>
str_view(c("abc", "a.c", "a*c", "a c"), "a[ ]")
[4] │ <a >c

Task-2: Visualizing patterns matching “grey” or “gray” in the provided character vector.

str_view(c("grey", "gray"), "gr(e|a)y")
[1] │ <grey>
[2] │ <gray>

Repetition

Task-1:Identifying patterns “CC” or “C” in the string “1888 is the longest year in Roman numerals

x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
str_view(x, "CC?")
[1] │ 1888 is the longest year in Roman numerals: MD<CC><C>LXXXVIII

Task-2: Viewing the pattern “CC”

str_view(x, "CC+")
[1] │ 1888 is the longest year in Roman numerals: MD<CCC>LXXXVIII

Task-3: Viewing the pattern “C[LX]+”

str_view(x, 'C[LX]+')
[1] │ 1888 is the longest year in Roman numerals: MDCC<CLXXX>VIII

Task-4:Viewing the pattern “C{2},C{2,},c{2,3}”

str_view(x, "C{2}")
[1] │ 1888 is the longest year in Roman numerals: MD<CC>CLXXXVIII
str_view(x, "C{2,}")
[1] │ 1888 is the longest year in Roman numerals: MD<CCC>LXXXVIII
str_view(x, "C{2,3}")
[1] │ 1888 is the longest year in Roman numerals: MD<CCC>LXXXVIII

Grouping and backreferences

Task-1:Grouping

str_view(fruit, "(..)\\1", match = TRUE)
 [4] │ b<anan>a
[20] │ <coco>nut
[22] │ <cucu>mber
[41] │ <juju>be
[56] │ <papa>ya
[73] │ s<alal> berry

Detect matches

Task-1: Checking for the presence of the letter “e” in each word

x <- c("apple", "banana", "pear")
str_detect(x, "e")
[1]  TRUE FALSE  TRUE

Task-2:Checking how many common words start with t

sum(str_detect(words, "^t"))
[1] 65

Task-3: Checking proportion of common words end with a vowel

mean(str_detect(words, "[aeiou]$"))
[1] 0.2765306

Task-4:Finding all words containing at least one vowel, and negate

no_vowels_1 <- !str_detect(words, "[aeiou]")

Task-5:Finding all words consisting only of consonants (non-vowels)

no_vowels_2 <- str_detect(words, "^[^aeiou]+$")
identical(no_vowels_1, no_vowels_2)
[1] TRUE

Task-6: Filtering words that end with the letter “x” from a list of words.

words[str_detect(words, "x$")]
[1] "box" "sex" "six" "tax"
str_subset(words, "x$")
[1] "box" "sex" "six" "tax"

Task-7: Filtering a tibble for words that end with “x”.

df <- tibble(
  word = words, 
  i = seq_along(word)
)
df %>% 
  filter(str_detect(word, "x$"))

Task-8:Counting the occurrences of “a” in each element of a character vector.

x <- c("apple", "banana", "pear")
str_count(x, "a")
[1] 1 3 1

Task-9: Seeing average of how many vowels per word

mean(str_count(words, "[aeiou]"))
[1] 1.991837

Task-10: Adding columns to a tibble to count vowels and consonants in each word.

df %>% 
  mutate(
    vowels = str_count(word, "[aeiou]"),
    consonants = str_count(word, "[^aeiou]")
  )

Task-11:Counting “aba” occurrences in “abababa” and showing all “aba” instances.

str_count("abababa", "aba")
[1] 2
str_view_all("abababa", "aba")
Warning: `str_view_all()` was deprecated in stringr 1.5.0.
Please use `str_view()` instead.
[1] │ <aba>b<aba>

Extract matches

Task-1: Displaying the length of sentences and showing the first few sentences.

length(sentences)
[1] 720
head(sentences)
[1] "The birch canoe slid on the smooth planks."  "Glue the sheet to the dark blue background."
[3] "It's easy to tell the depth of a well."      "These days a chicken leg is a rare dish."   
[5] "Rice is often served in round bowls."        "The juice of lemons makes fine punch."      

Task-2: Creating a string pattern to match colors by concatenating them with a pipe delimiter.

colours <- c("red", "orange", "yellow", "green", "blue", "purple")
colour_match <- str_c(colours, collapse = "|")
colour_match
[1] "red|orange|yellow|green|blue|purple"

Task-3: Filter sentences for colors and extract matches, showing the first few.

has_colour <- str_subset(sentences, colour_match)
matches <- str_extract(has_colour, colour_match)
head(matches)
[1] "blue" "blue" "red"  "red"  "red"  "blue"

Task-4:Showing all sentences containing multiple colors and highlight the matches.

more <- sentences[str_count(sentences, colour_match) > 1]
str_view_all(more, colour_match)
[1] │ It is hard to erase <blue> or <red> ink.
[2] │ The <green> light in the brown box flicke<red>.
[3] │ The sky in the west is tinged with <orange> <red>.

Task-5:Extracting all color matches from the subset of sentences containing multiple colors.

str_extract(more, colour_match)
[1] "blue"   "green"  "orange"

Task-6:Extracting all occurrences of colors from the subset of sentences containing multiple colors.

str_extract_all(more, colour_match)
[[1]]
[1] "blue" "red" 

[[2]]
[1] "green" "red"  

[[3]]
[1] "orange" "red"   

Task-7: Extracting colors from sentences with multiple colors and simplify, also extract lowercase letters from each element in x and simplify.

str_extract_all(more, colour_match, simplify = TRUE)
     [,1]     [,2] 
[1,] "blue"   "red"
[2,] "green"  "red"
[3,] "orange" "red"
x <- c("a", "a b", "a b c")
str_extract_all(x, "[a-z]", simplify = TRUE)
     [,1] [,2] [,3]
[1,] "a"  ""   ""  
[2,] "a"  "b"  ""  
[3,] "a"  "b"  "c" 

Grouped matches

Task-1: Extracting sentences containing nouns defined by a pattern, then extracts the nouns from those sentences.

noun <- "(a|the) ([^ ]+)"

has_noun <- sentences %>%
  str_subset(noun) %>%
  head(10)
has_noun %>% 
  str_extract(noun)
 [1] "the smooth" "the sheet"  "the depth"  "a chicken"  "the parked" "the sun"    "the huge"   "the ball"  
 [9] "the woman"  "a helps"   

Task-2:

has_noun %>% 
  str_match(noun)
      [,1]         [,2]  [,3]     
 [1,] "the smooth" "the" "smooth" 
 [2,] "the sheet"  "the" "sheet"  
 [3,] "the depth"  "the" "depth"  
 [4,] "a chicken"  "a"   "chicken"
 [5,] "the parked" "the" "parked" 
 [6,] "the sun"    "the" "sun"    
 [7,] "the huge"   "the" "huge"   
 [8,] "the ball"   "the" "ball"   
 [9,] "the woman"  "the" "woman"  
[10,] "a helps"    "a"   "helps"  

Task-3:Creating a tibble with columns ‘article’ and ‘noun’ extracted from sentences based on a pattern.

tibble(sentence = sentences) %>% 
  tidyr::extract(
    sentence, c("article", "noun"), "(a|the) ([^ ]+)", 
    remove = FALSE
  )

Replacing matches

Task-1: Replacing the first vowel in each word of x with a hyphen. Replacing all vowels in each word of x with a hyphen.

x <- c("apple", "pear", "banana")
str_replace(x, "[aeiou]", "-")
[1] "-pple"  "p-ar"   "b-nana"
str_replace_all(x, "[aeiou]", "-")
[1] "-ppl-"  "p--r"   "b-n-n-"

Task-2: Replacing numeric values in x with their corresponding word representations.

x <- c("1 house", "2 cars", "3 people")
str_replace_all(x, c("1" = "one", "2" = "two", "3" = "three"))
[1] "one house"    "two cars"     "three people"

Task-3:Reordering words in sentences by swapping the second and third word positions.

sentences %>% 
  str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") %>% 
  head(5)
[1] "The canoe birch slid on the smooth planks."  "Glue sheet the to the dark blue background."
[3] "It's to easy tell the depth of a well."      "These a days chicken leg is a rare dish."   
[5] "Rice often is served in round bowls."       

Splitting

Task-1: Splitting the first five sentences into words.

sentences %>%
  head(5) %>% 
  str_split(" ")
[[1]]
[1] "The"     "birch"   "canoe"   "slid"    "on"      "the"     "smooth"  "planks."

[[2]]
[1] "Glue"        "the"         "sheet"       "to"          "the"         "dark"        "blue"       
[8] "background."

[[3]]
[1] "It's"  "easy"  "to"    "tell"  "the"   "depth" "of"    "a"     "well."

[[4]]
[1] "These"   "days"    "a"       "chicken" "leg"     "is"      "a"       "rare"    "dish."  

[[5]]
[1] "Rice"   "is"     "often"  "served" "in"     "round"  "bowls."

Task-2:Splitting the string ‘a|b|c|d’ by ‘|’ into a vector of elements.

"a|b|c|d" %>% 
  str_split("\\|") %>% 
  .[[1]]
[1] "a" "b" "c" "d"

Task-3:Splitting the first 5 sentences by space into a matrix of words.

sentences %>%
  head(5) %>% 
  str_split(" ", simplify = TRUE)
     [,1]    [,2]    [,3]    [,4]      [,5]  [,6]    [,7]     [,8]          [,9]   
[1,] "The"   "birch" "canoe" "slid"    "on"  "the"   "smooth" "planks."     ""     
[2,] "Glue"  "the"   "sheet" "to"      "the" "dark"  "blue"   "background." ""     
[3,] "It's"  "easy"  "to"    "tell"    "the" "depth" "of"     "a"           "well."
[4,] "These" "days"  "a"     "chicken" "leg" "is"    "a"      "rare"        "dish."
[5,] "Rice"  "is"    "often" "served"  "in"  "round" "bowls." ""            ""     

Task-4:Splitting each field string into two parts at the first occurrence of ‘:’.

fields <- c("Name: Hadley", "Country: NZ", "Age: 35")
fields %>% str_split(": ", n = 2, simplify = TRUE)
     [,1]      [,2]    
[1,] "Name"    "Hadley"
[2,] "Country" "NZ"    
[3,] "Age"     "35"    

Task-5: Display word boundaries, split by spaces, and split by word boundaries, respectively.

x <- "This is a sentence.  This is another sentence."
str_view_all(x, boundary("word"))
[1] │ <This> <is> <a> <sentence>.  <This> <is> <another> <sentence>.
str_split(x, " ")[[1]]
[1] "This"      "is"        "a"         "sentence." ""          "This"      "is"        "another"  
[9] "sentence."
str_split(x, boundary("word"))[[1]]
[1] "This"     "is"       "a"        "sentence" "This"     "is"       "another"  "sentence"

Other types of pattern

Task-1:

# The regular call:
str_view(fruit, "nana")
[4] │ ba<nana>
# Is shorthand for
str_view(fruit, regex("nana"))
[4] │ ba<nana>

Task-2:Visualizing occurrences of “banana” in different case variations.

bananas <- c("banana", "Banana", "BANANA")
str_view(bananas, "banana")
[1] │ <banana>
str_view(bananas, regex("banana", ignore_case = TRUE))
[1] │ <banana>
[2] │ <Banana>
[3] │ <BANANA>

Task-3: Extracting all lines starting with “Line” from the text.

x <- "Line 1\nLine 2\nLine 3"
str_extract_all(x, "^Line")[[1]]
[1] "Line"

Task-4: Extracting all occurrences of lines starting with “Line” from the text, considering each line separately.

str_extract_all(x, regex("^Line", multiline = TRUE))[[1]]
[1] "Line" "Line" "Line"

Task-5:Creating a regular expression pattern for phone numbers, allowing for variations in formatting, and attempting to match it against the provided phone number.

phone <- regex("
  \\(?     # optional opening parens
  (\\d{3}) # area code
  [) -]?   # optional closing parens, space, or dash
  (\\d{3}) # another three numbers
  [ -]?    # optional space or dash
  (\\d{3}) # three more numbers
  ", comments = TRUE)

str_match("514-791-8141", phone)
     [,1]          [,2]  [,3]  [,4] 
[1,] "514-791-814" "514" "791" "814"

Task-6:Installling the package and Benchmarking string detection in “sentences” using fixed and regex patterns 20 times each, comparing performance with microbenchmark.


package_to_install <- c("microbenchmark")

for (package_name in package_to_install) {
  if (!requireNamespace(package_name, quietly = TRUE)) {
    install.packages(package_name)
  }
}
library(microbenchmark)

microbenchmark::microbenchmark(
  fixed = str_detect(sentences, fixed("the")),
  regex = str_detect(sentences, "the"),
  times = 20
  )
Unit: microseconds

Task-7:Starting with a1 being “0e1” and a2 being “a301”, both representing the character “á”, they are compared for equality.

a1 <- "\u00e1"
a2 <- "a\u0301"
c(a1, a2)
[1] "á" "á"
a1 == a2
[1] FALSE

Task-8: Checking if a1 contains the fixed string a2 returns FALSE, whereas using collation rules returns TRUE.

str_detect(a1, fixed(a2))
[1] FALSE
str_detect(a1, coll(a2))
[1] TRUE

Task-9:Creating a vector i with different forms of the letter “i”, then using str_subset to filter them based on collation.

i <- c("I", "İ", "i", "ı")
i
[1] "I" "İ" "i" "ı"
str_subset(i, coll("i", ignore_case = TRUE))
[1] "I" "i"
str_subset(i, coll("i", ignore_case = TRUE, locale = "tr"))
[1] "İ" "i"

Task-10: Fetching locale information.

stringi::stri_locale_info()
$Language
[1] "en"

$Country
[1] "US"

$Variant
[1] ""

$Name
[1] "en_US"

Task-11:Visualizing word boundaries and extracts all words from the string.

x <- "This is a sentence."
str_view_all(x, boundary("word"))
[1] │ <This> <is> <a> <sentence>.
str_extract_all(x, boundary("word"))
[[1]]
[1] "This"     "is"       "a"        "sentence"

CH-15: Factors

Creatig factors

Task-1:Adding character vector in variable x1

x1 <- c("Dec", "Apr", "Jan", "Mar")

Task-2:Adding character vector in variable x2

x2 <- c("Dec", "Apr", "Jam", "Mar")

Task-3:Sorting X1

sort(x1)
[1] "Apr" "Dec" "Jan" "Mar"

Task-4:Adding Character vector in month_levels

month_levels <- c(
  "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)

Task-5:Assigning the factor levels to the variable x1, using the predefined month_levels.

y1 <- factor(x1, levels = month_levels)
y

Task-6:Sorting the factor levels in y1.

sort(y1)
[1] Jan Mar Apr Dec
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

Task-7:creating a factor y2 from x2 with custom levels specified by month_levels.

y2 <- factor(x2, levels = month_levels)
y2
[1] Dec  Apr  <NA> Mar 
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec

Task-8:parsing the values in x2 as factors

y2 <- parse_factor(x2, levels = month_levels)
Warning: 1 parsing failure.
row col           expected actual
  3  -- value in level set    Jam

Task-9: omitting the levels.

factor(x1)
[1] Dec Apr Jan Mar
Levels: Apr Dec Jan Mar

Task-10:Creating a factor f1 from the values in x1, using the unique values of x1 as levels.

f1 <- factor(x1, levels = unique(x1))
f1
[1] Dec Apr Jan Mar
Levels: Dec Apr Jan Mar

Task-11: creating a factor f2 from the values in x1, ordering them according to their appearance in x1.

f2 <- x1 %>% factor() %>% fct_inorder()
f2
[1] Dec Apr Jan Mar
Levels: Dec Apr Jan Mar

Task-12:Omitting levels2

levels(f2)
[1] "Dec" "Apr" "Jan" "Mar"

General Social Survey

Task-1:Loading datasets

gss_cat

Task-2:Seeing levels through count()

gss_cat %>%
  count(race)

Task-3:Also seeing through bar()

ggplot(gss_cat, aes(race)) +
  geom_bar()

Task-4:Generating a bar plot using ggplot()

ggplot(gss_cat,aes(race))+geom_bar()+scale_x_discrete(drop=FALSE)

Modifying factor order

Task-1:calculating summary statistics and then creating scatter plot

relig_summary <- gss_cat %>%
  group_by(relig) %>%
  summarise(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
    n = n()
  )

ggplot(relig_summary, aes(tvhours, relig)) + geom_point()

Task-2:Generating a scatter plot using ggplot, where the x-axis represents the mean TV hours (tvhours), and the y-axis represents the relig variable reordered by mean TV hours.

ggplot(relig_summary, aes(tvhours, fct_reorder(relig, tvhours))) +
  geom_point()

Task-3:Creating a scatter plot using ggplot.

relig_summary %>%
  mutate(relig = fct_reorder(relig, tvhours)) %>%
  ggplot(aes(tvhours, relig)) +
    geom_point()

Task-4:Generating a scatter plot using ggplot

rincome_summary <- gss_cat %>%
  group_by(rincome) %>%
  summarise(
    age = mean(age, na.rm = TRUE),
    tvhours = mean(tvhours, na.rm = TRUE),
    n = n()
  )

ggplot(rincome_summary, aes(age, fct_reorder(rincome, age))) + geom_point()

Task-5: creates a scatter plot of the average age by income level, with “Not applicable” as the reference level for income

ggplot(rincome_summary, aes(age, fct_relevel(rincome, "Not applicable"))) +
  geom_point()

Task-6:calculating the proportion of each marital status group across different age groups and creates a line plot showing the distribution of marital status proportions by age.

by_age <- gss_cat %>%
  filter(!is.na(age)) %>%
  count(age, marital) %>%
  group_by(age) %>%
  mutate(prop = n / sum(n))

ggplot(by_age, aes(age, prop, colour = marital)) +
  geom_line(na.rm = TRUE)


ggplot(by_age, aes(age, prop, colour = fct_reorder2(marital, age, prop))) +
  geom_line() +
  labs(colour = "marital")

Task-7: Adjusting the order of the “marital” variable based on frequency and then reverses the order before generating a bar plot illustrating the distribution of marital status.

gss_cat %>%
  mutate(marital = marital %>% fct_infreq() %>% fct_rev()) %>%
  ggplot(aes(marital)) +
    geom_bar()

Modifying factor levels

Task-1: counting the frequency of each unique value in the “partyid” variable of the “gss_cat” dataset.

gss_cat%>%count(partyid)

Task-2:Recording the levels of the “partyid” variable in the “gss_cat” dataset and then counts the frequency of each unique recorded value.

gss_cat %>%
  mutate( partyid=fct_recode(partyid,
    "Republican, strong"    = "Strong republican",
    "Republican, weak"      = "Not str republican",
    "Independent, near rep" = "Ind,near rep",
    "Independent, near dem" = "Ind,near dem",
    "Democrat, weak"        = "Not str democrat",
    "Democrat, strong"      = "Strong democrat"
    ))%>%
  count(partyid)

Task-3:Recategorizing and counting party affiliations in the “gss_cat” dataset.

gss_cat %>%
  mutate(partyid = fct_recode(partyid,
    "Republican, strong"    = "Strong republican",
    "Republican, weak"      = "Not str republican",
    "Independent, near rep" = "Ind,near rep",
    "Independent, near dem" = "Ind,near dem",
    "Democrat, weak"        = "Not str democrat",
    "Democrat, strong"      = "Strong democrat",
    "Other"                 = "No answer",
    "Other"                 = "Don't know",
    "Other"                 = "Other party"
  )) %>%
  count(partyid)

Task-4: Collapsing categories within the “partyid” variable in the “gss_cat” dataset into broader groups and then counting the frequency of each collapsed category.

gss_cat%>%
  mutate(partyid=fct_collapse(partyid,
                              other=c("No answer", "Don't know", "Other party"),
                              rep=c("Strong republican", "Not str republican"),
                              ind=c("Ind,near rep", "Independent", "Ind,near dem"),
                              dem=c("Not str democrat", "Strong democrat"))) %>%
  count(partyid)

Task-5:Counting and aggregating religious affiliations in the “gss_cat” dataset after lumping together less frequent categories.

gss_cat %>%
  mutate(relig = fct_lump(relig)) %>%
  count(relig)

Task-6:“Summarizing religious affiliations after lumping infrequent categories and sort.”

gss_cat %>%
  mutate(relig = fct_lump(relig, n = 10)) %>%
  count(relig, sort = TRUE) %>%
  print(n = Inf)

CH-Data and Times

Task-1:Loading library

library(tidyverse)

library(lubridate)
library(nycflights13)

Creating dates/times

Task-1: Printing current date or date-time

today()
[1] "2024-05-04"
now()
[1] "2024-05-04 20:51:16 +0545"

Form strings

Task-2:converting date strings to date objects in different formats.

ymd("2017-01-31")
[1] "2017-01-31"
mdy("January 31st, 2017")
[1] "2017-01-31"
dmy("31-Jan-2017")
[1] "2017-01-31"
ymd(20170131)
[1] "2017-01-31"
ymd_hms("2017-01-31 20:11:59")
[1] "2017-01-31 20:11:59 UTC"
mdy_hm("01/31/2017 08:01")
[1] "2017-01-31 08:01:00 UTC"
flights %>% 
  select(year, month, day, hour, minute)
flights %>% 
  select(year, month, day, hour, minute) %>% 
  mutate(departure = make_datetime(year, month, day, hour, minute))

Task: Creating date-time objects from hour-minute time data in the ‘flights’ dataset and filtering out rows with missing departure or arrival times

make_datetime_100 <- function(year, month, day, time) {
  make_datetime(year, month, day, time %/% 100, time %% 100)
}

flights_dt <- flights %>% 
  filter(!is.na(dep_time), !is.na(arr_time)) %>% 
  mutate(
    dep_time = make_datetime_100(year, month, day, dep_time),
    arr_time = make_datetime_100(year, month, day, arr_time),
    sched_dep_time = make_datetime_100(year, month, day, sched_dep_time),
    sched_arr_time = make_datetime_100(year, month, day, sched_arr_time)
  ) %>% 
  select(origin, dest, ends_with("delay"), ends_with("time"))

flights_dt

Task: Plotting the frequency of flights over time using departure date-time

flights_dt %>% 
  ggplot(aes(dep_time)) + 
  geom_freqpoly(binwidth = 86400) 

Task: Plotting the frequency of flights over time for a specific period using departure date-time

flights_dt %>% 
  filter(dep_time < ymd(20130102)) %>% 
  ggplot(aes(dep_time)) + 
  geom_freqpoly(binwidth = 600) # 600 s = 10 minutes

Task: to convert today’s date to date-time object

as_datetime(today())
[1] "2024-05-04 UTC"
as_date(now())
[1] "2024-05-04"
as_date(365 * 10 + 2)
[1] "1980-01-01"

Date-time components Task: Extracting various components of a date-time object

datetime <- ymd_hms("2016-07-08 12:34:56")
year(datetime)
[1] 2016
month(datetime)
[1] 7
mday(datetime)
[1] 8
yday(datetime)
[1] 190
wday(datetime)
[1] 6
month(datetime, label = TRUE)
[1] Jul
Levels: Jan < Feb < Mar < Apr < May < Jun < Jul < Aug < Sep < Oct < Nov < Dec
wday(datetime, label = TRUE, abbr = FALSE)
[1] Friday
Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < Friday < Saturday

Task: Plotting the frequency of flights by day of the week

flights_dt %>% 
  mutate(wday = wday(dep_time, label = TRUE)) %>% 
  ggplot(aes(x = wday)) +
    geom_bar()

Task: Plotting average delay by minute of departure time

flights_dt %>% 
  mutate(minute = minute(dep_time)) %>% 
  group_by(minute) %>% 
  summarise(
    avg_delay = mean(arr_delay, na.rm = TRUE),
    n = n()) %>% 
  ggplot(aes(minute, avg_delay)) +
    geom_line()

Task: Plotting average delay by minute of scheduled departure time

sched_dep <- flights_dt %>% 
  mutate(minute = minute(sched_dep_time)) %>% 
  group_by(minute) %>% 
  summarise(
    avg_delay = mean(arr_delay, na.rm = TRUE),
    n = n())

ggplot(sched_dep, aes(minute, avg_delay)) +
  geom_line()

Task: Plotting the number of flights by minute of scheduled departure time

ggplot(sched_dep, aes(minute, n)) +
  geom_line()

Rounding Task:Plotting the number of flights by week, rounding to the nearest week

flights_dt %>% 
  count(week = floor_date(dep_time, "week")) %>% 
  ggplot(aes(week, n)) +
    geom_line()

setting compounds Task: Setting up a date-time object

(datetime <- ymd_hms("2016-07-08 12:34:56"))
[1] "2016-07-08 12:34:56 UTC"
year(datetime) <- 2020
datetime
[1] "2020-07-08 12:34:56 UTC"
month(datetime) <- 01
datetime
[1] "2020-01-08 12:34:56 UTC"
hour(datetime) <- hour(datetime) + 1
datetime
[1] "2020-01-08 13:34:56 UTC"
update(datetime, year = 2020, month = 2, mday = 2, hour = 2)
[1] "2020-02-02 02:34:56 UTC"
ymd("2015-02-01") %>% 
  update(mday = 30)
[1] "2015-03-02"
ymd("2015-02-01") %>% 
  update(hour = 400)
[1] "2015-02-17 16:00:00 UTC"

Task: Creating a new variable ‘dep_hour’ by updating the ‘dep_time’ to the first day of the year

flights_dt %>% 
  mutate(dep_hour = update(dep_time, yday = 1)) %>% 
  ggplot(aes(dep_hour)) +
    geom_freqpoly(binwidth = 300)

Time Spans Compute the age of a person based on their birthdate and today’s date

h_age <- today() - ymd(19791014)
h_age
Time difference of 16274 days
as.duration(h_age)
[1] "1406073600s (~44.56 years)"
dseconds(15)
[1] "15s"
dminutes(10)
[1] "600s (~10 minutes)"
dhours(c(12, 24))
[1] "43200s (~12 hours)" "86400s (~1 days)"  
ddays(0:5)
[1] "0s"                "86400s (~1 days)"  "172800s (~2 days)" "259200s (~3 days)" "345600s (~4 days)"
[6] "432000s (~5 days)"
dweeks(3)
[1] "1814400s (~3 weeks)"
dyears(1)
[1] "31557600s (~1 years)"
2 * dyears(1)
[1] "63115200s (~2 years)"
dyears(1) + dweeks(12) + dhours(15)
[1] "38869200s (~1.23 years)"
tomorrow <- today() + ddays(1)
last_year <- today() - dyears(1)
one_pm <- ymd_hms("2016-03-12 13:00:00", tz = "America/New_York")
one_pm
[1] "2016-03-12 13:00:00 EST"
one_pm + ddays(1)
[1] "2016-03-13 14:00:00 EDT"

Periods Create period objects representing different time spans and Perform arithmetic operations with period objects

one_pm
[1] "2016-03-12 13:00:00 EST"
one_om = days(1)
seconds(15)
[1] "15S"
minutes(10)
[1] "10M 0S"
hours(c(12, 24))
[1] "12H 0M 0S" "24H 0M 0S"
days(7)
[1] "7d 0H 0M 0S"
months(1:6)
[1] "1m 0d 0H 0M 0S" "2m 0d 0H 0M 0S" "3m 0d 0H 0M 0S" "4m 0d 0H 0M 0S" "5m 0d 0H 0M 0S" "6m 0d 0H 0M 0S"
weeks(3)
[1] "21d 0H 0M 0S"
years(1)
[1] "1y 0m 0d 0H 0M 0S"
10 * (months(6) + days(1))
[1] "60m 10d 0H 0M 0S"
days(50) + hours(25) + minutes(2)
[1] "50d 25H 2M 0S"
ymd("2016-01-01") + dyears(1)
[1] "2016-12-31 06:00:00 UTC"
ymd("2016-01-01") + years(1)
[1] "2017-01-01"
one_pm + ddays(1)
[1] "2016-03-13 14:00:00 EDT"
one_pm + days(1)
[1] "2016-03-13 13:00:00 EDT"

Filter flights where arrival time is before departure time

flights_dt %>% 
  filter(arr_time < dep_time) 

Update flights data to correct overnight flights

flights_dt <- flights_dt %>% 
  mutate(
    overnight = arr_time < dep_time,
    arr_time = arr_time + days(overnight * 1),
    sched_arr_time = sched_arr_time + days(overnight * 1)
  )

Filter flights where overnight condition is true and arrival time is before departure time

flights_dt %>% 
  filter(overnight, arr_time < dep_time) 

Intervals Calculate the ratio of one year in days

years(1) / days(1)
[1] 365.25
next_year <- today() + years(1)
(today() %--% next_year) / ddays(1)
[1] 365
(today() %--% next_year) %/% days(1)
[1] 365

Display time zone information

Sys.timezone()
[1] "Asia/Katmandu"
length(OlsonNames())
[1] 596
head(OlsonNames())
[1] "Africa/Abidjan"     "Africa/Accra"       "Africa/Addis_Ababa" "Africa/Algiers"     "Africa/Asmara"     
[6] "Africa/Asmera"     
(x1 <- ymd_hms("2015-06-01 12:00:00", tz = "America/New_York"))
[1] "2015-06-01 12:00:00 EDT"
(x2 <- ymd_hms("2015-06-01 18:00:00", tz = "Europe/Copenhagen"))
[1] "2015-06-01 18:00:00 CEST"
(x3 <- ymd_hms("2015-06-02 04:00:00", tz = "Pacific/Auckland"))
[1] "2015-06-02 04:00:00 NZST"
x1 - x2
Time difference of 0 secs
x1 - x3
Time difference of 0 secs

Pipes

Task: To import the required library

packages_to_install <- c("tidyverse", "pryr")
for (package_name in packages_to_install) {
  if (!requireNamespace(package_name, quietly = TRUE)) {
    install.packages(package_name)
  }
  library(package_name, character.only = TRUE)
}

library(magrittr)

Create diamond data and calculate the object sizes

diamonds <- ggplot2::diamonds
diamonds2 <- diamonds %>% 
  dplyr::mutate(price_per_carat = price / carat)

pryr::object_size(diamonds)
3.46 MB
pryr::object_size(diamonds2)
3.89 MB
pryr::object_size(diamonds, diamonds2)
3.89 MB

Functions Normalize the columns of a data frame

df <- tibble::tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)

df$a <- (df$a - min(df$a, na.rm = TRUE)) / 
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) / 
  (max(df$b, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) / 
  (max(df$c, na.rm = TRUE) - min(df$c, na.rm = TRUE))
df$d <- (df$d - min(df$d, na.rm = TRUE)) / 
  (max(df$d, na.rm = TRUE) - min(df$d, na.rm = TRUE))

Normalize a single column of a data frame

(df$a - min(df$a, na.rm = TRUE)) /
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
 [1] 0.2660918 0.1288832 0.0769690 0.3163641 0.5612945 0.6241704 0.5271891 0.0000000 0.3913369 1.0000000
x <- df$a
(x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
 [1] 0.2660918 0.1288832 0.0769690 0.3163641 0.5612945 0.6241704 0.5271891 0.0000000 0.3913369 1.0000000
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
 [1] 0.2660918 0.1288832 0.0769690 0.3163641 0.5612945 0.6241704 0.5271891 0.0000000 0.3913369 1.0000000
rescale01 <- function(x) {
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}
rescale01(c(0, 5, 10))
[1] 0.0 0.5 1.0

Rescale a vector to the range [0, 1]

rescale01(c(-10, 0, 10))
[1] 0.0 0.5 1.0
rescale01(c(1, 2, 3, NA, 5))
[1] 0.00 0.25 0.50   NA 1.00

Rescale each column of a DataFrame to the range [0, 1]

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)
x <- c(1:10, Inf)
rescale01(x)
 [1]   0   0   0   0   0   0   0   0   0   0 NaN

Define the rescale01 function and apply it

rescale01 <- function(x) {
  rng <- range(x, na.rm = TRUE, finite = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}
rescale01(x)
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 0.8888889 1.0000000
[11]       Inf

Load required libraries and packages

library(tidyverse)
library(purrr)
library(magrittr)

# install.packages("pryr")
library(pryr)

18.2 Piping alternatives

This is a popular Children’s poem that is accompanied by hand actions.We’ll start by defining an object to represent little bunny Foo Foo:

# foo_foo <- little_bunny()

18.2.1 Intermediate steps

The simplest approach is to save each step as a new object:

# foo_foo_1 <- hop(foo_foo,through=forest)
# foo_foo_2 <- scoop(foo_foo_1, up = field_mice)
# foo_foo_3 <- bop(foo_foo_2, on = head)

Create diamonds dataset and calculate price per carat

diamonds <- ggplot2::diamonds
diamonds2 <- diamonds %>% 
  dplyr::mutate(price_per_carat=price/carat)

pryr::object_size(diamonds)
3.46 MB
pryr::object_size(diamonds2)
3.89 MB
pryr::object_size(diamonds,diamonds2)
3.89 MB

Introduce NA value into diamonds$carat and check object sizes

diamonds$carat[1] <- NA
pryr::object_size(diamonds)
3.46 MB
pryr::object_size(diamonds2)
3.89 MB
pryr::object_size(diamonds,diamonds2)
4.32 MB

18.2.2 Overwrite the original

Instead of creating intermediate objects at each step, we could overwrite the original object:

# foo_foo <- hop(foo_foo, through = forest)
# foo_foo <- scoop(foo_foo, up = field_mice)
# foo_foo <- bop(foo_foo, on = head)

18.2.3 Function composition

Another approach is to abandon assignment and just string the function calls together:

# bop(
#   scoop(
#     hop(foo_foo, through = forest),
#     up = field_mice
#   ), 
#   on = head
# )

Here the disadvantage is that you have to read from inside-out, from right-to-left, and that the arguments end up spread far apart (evocatively called the dagwood sandwhich problem). In short, this code is hard for a human to consume.

18.2.4 Use the pipe

Finally, we can use the pipe:

# foo_foo %>%
#   hop(through = forest) %>%
#   scoop(up = field_mice) %>%
#   bop(on = head)
# my_pipe <- function(.) {
#   . <- hop(., through = forest)
#   . <- scoop(., up = field_mice)
#   bop(., on = head)
# }
# my_pipe(foo_foo)

TASK: Functions that use the current environment. For example, assign() will create a new variable with the given name in the current environment:

assign("x",10)
x
[1] 10
"x" %>% assign(100)
x
[1] 10

Assign value to “x” in the specified environment and check its value and Generate random numbers, create a matrix, plot it, and inspect its structure

env <- environment()
"x" %>% assign(100,envir=env)
x
[1] 100
rnorm(100) %>% 
  matrix(ncol=2) %>% 
  plot() %>% 
  str()
 NULL

rnorm(100) %>% 
  matrix(ncol=2) %>% 
  plot() %>% 
  str()
 NULL

ndist <- rnorm(100000)
hist(ndist)

Calculate the correlation between two variables in mtcars dataset

mtcars %$%
  cor(disp, mpg)
[1] -0.8475514
  • For assignment magrittr provides the %<>% operator which allows you to replace code like:
mtcars <- mtcars %>% 
  transform(cyl=cyl*2)
mtcars %<>% transform(cyl=cyl*2)

Chapter 19 Functions

19.1 Introduction

19.2 When should you write a function?

df <- tibble::tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)
df

df$a <- (df$a - min(df$a, na.rm = TRUE)) / 
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) / 
  (max(df$b, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) / 
  (max(df$c, na.rm = TRUE) - min(df$c, na.rm = TRUE))
df$d <- (df$d - min(df$d, na.rm = TRUE)) / 
  (max(df$d, na.rm = TRUE) - min(df$d, na.rm = TRUE))

Rescale a single variable in a data frame

(df$a - min(df$a, na.rm = TRUE)) /
  (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
 [1] 0.5235603 0.5627738 0.2777349 0.0000000 1.0000000 0.1019340 0.3832672 0.9030480 0.6526363 0.6825633

Rescale a single variable without creating a new object

x <- df$a
(x - min(x, na.rm = T)) / (max(x, na.rm = T)-min(x, na.rm = T))
 [1] 0.5235603 0.5627738 0.2777349 0.0000000 1.0000000 0.1019340 0.3832672 0.9030480 0.6526363 0.6825633

Task: There is some duplication in this code. We’re computing the range of the data three times, so it makes sense to do it in one step:

rng <- range(x, na.rm = T)
(x-rng[1])/(rng[2]-rng[1])
 [1] 0.5235603 0.5627738 0.2777349 0.0000000 1.0000000 0.1019340 0.3832672 0.9030480 0.6526363 0.6825633

Pulling out intermediate calculations into named variables is a good practice because it makes it more clear what the code is doing. Now that I’ve simplified the code, and checked that it still works, I can turn it into a function:

rescale01 <- function(x){
  rng <- range(x, na.rm = T)
  (x-rng[1])/(rng[2]-rng[1])
}
rescale01(c(0,5,10))
[1] 0.0 0.5 1.0

Test the rescale01 function with various inputs

rescale01(c(-10,0,10))
[1] 0.0 0.5 1.0
rescale01(c(1,2,3,NA,5))
[1] 0.00 0.25 0.50   NA 1.00

We can simplify the original example now that we have a function:

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)

Rescale a vector with infinite values

x <- c(1:10,Inf)
rescale01(x)
 [1]   0   0   0   0   0   0   0   0   0   0 NaN

Because we’ve extracted the code into a function, we only need to make the fix in one place:

rescale01 <- function(x){
  rng <- range(x,na.rm=T,finite=T)
  (x-rng[1])/(rng[2]-rng[1])
}
rescale01(x)
 [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667 0.7777778 0.8888889 1.0000000
[11]       Inf

19.4 Conditional execution

An if statement allows you to conditionally execute code. It looks like this:

# if (condition) {
  # code executed when condition is TRUE
# } else {
  # code executed when condition is FALSE
# }

Define a function to check if an object has names

has_name <- function(x){
  nms <- names(x)
  if(is.null(nms)){
    rep(FALSE,length(x))
  }else {
    !is.na(nms) & nms !=""
  }
}

19.4.1 Conditions

how if condition works with warnings

# if (c(TRUE,FALSE)){}
#> Warning in if (c(TRUE, FALSE)) {: the condition has length > 1 and only the
#> first element will be used
#> NULL

# if (NA) {}

Check if two objects are identical

identical(0L,0)
[1] FALSE
x <- sqrt(2)^2
x==2
[1] FALSE
x-2
[1] 4.440892e-16

19.4.2 Multiple conditions

You can chain multiple if statement together:

# if (this) {
#   # do that
# } else if (that) {
#   # do something else
# } else {
#   # 
# }
#> function(x, y, op) {
#>   switch(op,
#>     plus = x + y,
#>     minus = x - y,
#>     times = x * y,
#>     divide = x / y,
#>     stop("Unknown op!")
#>   )
#> }

19.4.3 Code style

Good practice for writing if statements

# Good
# if (y < 0 && debug) {
#   message("Y is negative")
# }
# 
# if (y == 0) {
#   log(x)
# } else {
#   y ^ x
# }
# 
# # Bad
# if (y < 0 && debug)
# message("Y is negative")
# 
# if (y == 0) {
#   log(x)
# } 
# else {
#   y ^ x
# }

It’s ok to drop the curly braces if you have a very short if statement that can fit on one line:

y <- 10
x <- if (y < 20) "Too low" else "Too high"

I recommend this only for very brief if statements. Otherwise, the full form is easier to read:

if (y < 20) {
  x <- "Too low" 
} else {
  x <- "Too high"
}

19.5 Function arguments

# Compute confidence interval around mean using normal approximation
mean_ci <- function(x, conf = 0.95) {
  se <- sd(x) / sqrt(length(x))
  alpha <- 1 - conf
  mean(x) + se * qnorm(c(alpha / 2, 1 - alpha / 2))
}

x <- runif(100)
mean_ci(x)
[1] 0.4370008 0.5485763
mean_ci(x, conf = 0.99)
[1] 0.4194710 0.5661061

19.5.1 Choosing names

19.5.2 Cheking values

wt_mean <- function(x, w) {
  sum(x * w) / sum(w)
}
wt_var <- function(x, w) {
  mu <- wt_mean(x, w)
  sum(w * (x - mu) ^ 2) / sum(w)
}
wt_sd <- function(x, w) {
  sqrt(wt_var(x, w))
}

What happens if x and w are not the same length?

wt_mean(1:6, 1:3)
[1] 7.666667

In this case, because of R’s vector recycling rules, we don’t get an error.

It’s good practice to check important preconditions, and throw an error (with stop()), if they are not true:

wt_mean <- function(x, w) {
  if (length(x) != length(w)) {
    stop("`x` and `w` must be the same length", call. = FALSE)
  }
  sum(w * x) / sum(w)
}
wt_mean <- function(x, w, na.rm = FALSE) {
  if (!is.logical(na.rm)) {
    stop("`na.rm` must be logical")
  }
  if (length(na.rm) != 1) {
    stop("`na.rm` must be length 1")
  }
  if (length(x) != length(w)) {
    stop("`x` and `w` must be the same length", call. = FALSE)
  }
  
  if (na.rm) {
    miss <- is.na(x) | is.na(w)
    x <- x[!miss]
    w <- w[!miss]
  }
  sum(w * x) / sum(w)
}

This is a lot of extra work for little additional gain. A useful compromise is the built-in stopifnot(): it checks that each argument is TRUE, and produces a generic error message if not.

wt_mean <- function(x, w, na.rm = FALSE) {
  stopifnot(is.logical(na.rm), length(na.rm) == 1)
  stopifnot(length(x) == length(w))
  
  if (na.rm) {
    miss <- is.na(x) | is.na(w)
    x <- x[!miss]
    w <- w[!miss]
  }
  sum(w * x) / sum(w)
}

19.5.3 Dot-dot-dot(…)

Many functions in R take an arbitrary number of inputs:

sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
[1] 55
stringr::str_c("a", "b", "c", "d", "e", "f")
[1] "abcdef"

Define a function to concatenate strings with commas

commas <- function(...) stringr::str_c(..., collapse = ", ")
commas(letters[1:10])
[1] "a, b, c, d, e, f, g, h, i, j"
rule <- function(..., pad = "-") {
  title <- paste0(...)
  width <- getOption("width") - nchar(title) - 5
  cat(title, " ", stringr::str_dup(pad, width), "\n", sep = "")
}
rule("Important output")
Important output ----------------------------------------------------------------------------------------
x <- c(1,2)
sum(x,na.rm=T)
[1] 3

Define a function ‘complicated_function’ with conditions to return 0 if ‘x’ or ‘y’ is empty

complicated_function <- function(x,y,z){
  if (lenth(x)==0 || length(y)==0){
    return(0)
  }
}

Improve readability of if-else blocks by using early return for simple cases

f <- function() {
  if (x) {
    # Do 
    # something
    # that
    # takes
    # many
    # lines
    # to
    # express
  } else {
    # return something short
  }
}

But if the first block is very long, by the time you get to the else, you’ve forgotten the condition. One way to rewrite it is to use an early return for the simple case:

f <- function() {
  if (!x) {
    return(something_short)
  }

  # Do 
  # something
  # that
  # takes
  # many
  # lines
  # to
  # express
}

This tends to make the code easier to understand, because you don’t need quite so much context to understand it.

19.6.2 Writing pipeable functions

Define a function to show the count of missing values in a data frame

show_missing <- function(df){
  n <- sum(is.na(df))
  cat("Missing values:",n,"\n",sep="")
  
  invisible(df)
}

If we call it interatively, the invisible() means that the input df does not get printed out:

show_missing(mtcars)
Missing values:0

But it’s still there, it’s not just printed by default:

x <- show_missing(mtcars)
Missing values:0
class(x)
[1] "data.frame"
dim(x)
[1] 32 11

And we can still use it in a pipe:

library(magrittr)
library(tidyverse)

mtcars %>% 
  show_missing() %>% 
  mutate(mpg=ifelse(mpg<20,NA,mpg)) %>% 
  show_missing()
Missing values:0
Missing values:18

19.7 Environment

Define a function ‘f’ that takes an argument ‘x’ and returns the sum of ‘x’ and ‘y’

f <- function(x){
  x+y
}

Demonstrate how changing the value of ‘y’ affects the result of calling function ‘f’

y <- 100
f(10)
[1] 110
y <- 1000
f(10)
[1] 1010

Overload the ‘+’ operator to behave differently based on a random condition

`+` <- function(x, y) {
  if (runif(1) < 0.1) {
    sum(x, y)
  } else {
    sum(x, y) * 1.1
  }
}
table(replicate(1000, 1 + 2))

  3 3.3 
 94 906 
#> 
#>   3 3.3 
#> 100 900
rm(`+`)

Chapter 20: Vectors

20.1.1 PRerequisites

library(tidyverse)

20.2 Vector basics

Determine the data type of different vectors

typeof(letters)
[1] "character"
typeof(1:10)
[1] "integer"

Determine the length of a list and display its contents

x <- list("a","b",1:10)
length(x)
[1] 3
x
[[1]]
[1] "a"

[[2]]
[1] "b"

[[3]]
 [1]  1  2  3  4  5  6  7  8  9 10

Demonstrate modulo operation and creation of logical vectors

1:10 %% 3 ==0
 [1] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
c(T,T,F,NA)
[1]  TRUE  TRUE FALSE    NA

20.3.2 Numeric

Integer and double vectors are known collectively as numeric vectors. In R, numbers are doubles by default. To make an integer, place an L after the number:

typeof(1)
[1] "double"
typeof(1L)
[1] "integer"
1.5
[1] 1.5

Demonstrate the behavior of floating point arithmetic

x <- sqrt(2)^2
x
[1] 2
x-2
[1] 4.440892e-16

Demonstrate the behavior of division by zero

c(-1,0,1)%/% 0
[1] -Inf  NaN  Inf
# [1] -Inf  NaN  Inf

20.3.3 Character

Determine the memory size of a string and a replicated string vector

x <- "This is a reasonably long string."
pryr::object_size(x)
152 B
y <- rep(x,1000)
pryr::object_size(y)
8.14 kB

20.3.4 Missing values

Note that each type of atomic vector has its own missing value:

NA            # logical
[1] NA
NA_integer_   # integer
[1] NA
NA_real_      # double
[1] NA
NA_character_ # character
[1] NA

Calculate the number and proportion of elements in a vector greater than 10

x <- sample(20,100,replace=T)
y <- x > 10
sum(y) # how many are greater than 10?
[1] 49
mean(y) # what proportion are greater than 10?
[1] 0.49
if (length(x)){
}
NULL

Determine the data type of different vectors

typeof(c(TRUE,1L))
[1] "integer"
typeof(c(1L,1.5))
[1] "double"
typeof(c(1.5,"a"))
[1] "character"

Generate random numeric or logical vectors

sample(10)+100
 [1] 110 103 104 101 102 107 109 106 105 108
runif(10)>0.5
 [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE

Demonstrate vector arithmetic with vectors of different lengths

1:10 +1:2
 [1]  2  4  4  6  6  8  8 10 10 12
1:10+1:3
Warning: longer object length is not a multiple of shorter object length
 [1]  2  4  6  5  7  9  8 10 12 11

Create a tibble with two columns, ‘x’ and ‘y’, with different lengths

library(tidyverse)



tibble(
  x=1:4,
  y=rep(1:2,each=2)
)

20.4.4 Naming vectors

All types of vectors can be named. You can name them during creatin with c():

c(x=1,y=2,z=4)
x y z 
1 2 4 

Or after the fact with purr::set_names()

set_names(1:3,c("a","b","c"))
a b c 
1 2 3 

Named vectors are most useful for subsetting, described next.

20.4.5 Subsetting

Demonstrate subsetting vectors with integer indices

x <- c("one","two","three","four","five")
x[c(3,2,5)]
[1] "three" "two"   "five" 

By repeating a position, you can actually make a longer output than input:

x[c(1,1,5,5,5,2)]
[1] "one"  "one"  "five" "five" "five" "two" 

Negative values drop the elements at the specified positions:

x[c(-1,-3,-5)]
[1] "two"  "four"

The error message mentions subsetting with zero, which returns no values:

x[0]
character(0)
library(tidyverse)
x <- c(10,3,NA,5,8,1)

# tibble test
x <- as.tibble(x,ncol=1)
Warning: `as.tibble()` was deprecated in tibble 2.0.0.
Please use `as_tibble()` instead.
The signature and semantics have changed, see `?as_tibble`.
names(x)="v1"
is.na(x)
        v1
[1,] FALSE
[2,] FALSE
[3,]  TRUE
[4,] FALSE
[5,] FALSE
[6,] FALSE
x %>% filter(v1 == NA)

# all non-missing values of x
x <- c(10,3,NA,5,8,1)
x[!is.na(x)]
[1] 10  3  5  8  1
# all even (or missing) values of x
x[x %% 2==0]
[1] 10 NA  8
  1. If you have a named vector, you can subset it with a character vector:
x <- c(abc=1, def=2,xyz=5)
x[c("xyz","def")]
xyz def 
  5   2 

20.5 Recursive vectors (lists)

Create a list with numeric elements

x <- list(1,2,3)
x
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

Display the structure of lists with and without names

str(x)
List of 3
 $ : num 1
 $ : num 2
 $ : num 3
x_named <- list(a=1,b=2,c=3)
str(x_named)
List of 3
 $ a: num 1
 $ b: num 2
 $ c: num 3

Unlike atomic vectors, list() can contain a mix of objects:

y <- list("a",1L,1.5,T)
str(y)
List of 4
 $ : chr "a"
 $ : int 1
 $ : num 1.5
 $ : logi TRUE

List can even contain other lists!

z <- list(list(1,2),list(3,4))
str(z)
List of 2
 $ :List of 2
  ..$ : num 1
  ..$ : num 2
 $ :List of 2
  ..$ : num 3
  ..$ : num 4

20.5.1 Visualizing lists

x1 <- list(c(1,2),c(3,4))
x2 <- list(list(1,2),list(3,4))
x3 <- list(1,list(2,list(3)))
x1
[[1]]
[1] 1 2

[[2]]
[1] 3 4
x2
[[1]]
[[1]][[1]]
[1] 1

[[1]][[2]]
[1] 2


[[2]]
[[2]][[1]]
[1] 3

[[2]][[2]]
[1] 4
x3
[[1]]
[1] 1

[[2]]
[[2]][[1]]
[1] 2

[[2]][[2]]
[[2]][[2]][[1]]
[1] 3

20.5.2 Subsetting

Create a list ‘a’ with named elements and demonstrate subsetting

a <- list(a = 1:3, b = "a string", c = pi, d = list(-1, -5))
str(a)
List of 4
 $ a: int [1:3] 1 2 3
 $ b: chr "a string"
 $ c: num 3.14
 $ d:List of 2
  ..$ : num -1
  ..$ : num -5
str(a[1:2])
List of 2
 $ a: int [1:3] 1 2 3
 $ b: chr "a string"
str(a[4])
List of 1
 $ d:List of 2
  ..$ : num -1
  ..$ : num -5

Demonstrate subsetting lists using double square brackets

str(a[[1]])
 int [1:3] 1 2 3
str(a[[4]])
List of 2
 $ : num -1
 $ : num -5

Access list elements by name using $ or [[ ]]

a$a
[1] 1 2 3
a[["a"]]
[1] 1 2 3

20.6 Attributes

Demonstrate setting and retrieving attributes of vectors

x <- 1:10
attr(x,"greeting")
NULL
attr(x,"greeting") <- "Hi!"
attr(x,"farewell") <- "Bye!"
attributes(x)
$greeting
[1] "Hi!"

$farewell
[1] "Bye!"

Demonstrate methods for class ‘Date’

as.Date
function (x, ...) 
UseMethod("as.Date")
<bytecode: 0x0000022ea4225378>
<environment: namespace:base>
methods("as.Date")
[1] as.Date.character   as.Date.default     as.Date.factor      as.Date.numeric     as.Date.POSIXct    
[6] as.Date.POSIXlt     as.Date.vctrs_sclr* as.Date.vctrs_vctr*
see '?methods' for accessing help and source code

Retrieve specific methods for ‘as.Date’

getS3method("as.Date","default")
function (x, ...) 
{
    if (inherits(x, "Date")) 
        x
    else if (is.null(x)) 
        .Date(numeric())
    else if (is.logical(x) && all(is.na(x))) 
        .Date(as.numeric(x))
    else stop(gettextf("do not know how to convert '%s' to class %s", 
        deparse1(substitute(x)), dQuote("Date")), domain = NA)
}
<bytecode: 0x0000022eb85b7c20>
<environment: namespace:base>
getS3method("as.Date","numeric")
function (x, origin, ...) 
if (missing(origin)) .Date(x) else as.Date(origin, ...) + x
<bytecode: 0x0000022ec2ac86e0>
<environment: namespace:base>

20.7.1 Factors

Demonstrate creating a factor and inspecting its attributes

x <- factor(c("ab","cd","ab"),levels=c("ab","cd","ed"))
typeof(x)
[1] "integer"
attributes(x)
$levels
[1] "ab" "cd" "ed"

$class
[1] "factor"

20.7.2 Dates and date-times

Dates in R are numeric vectors that represent the number of days since 1 January 1970.

x <- as.Date("1971-01-01")
unclass(x)
[1] 365
typeof(x)
[1] "double"
attributes(x)
$class
[1] "Date"

Demonstrate creating and inspecting a date-time object

x <- lubridate::ymd_hm("1970-01-01 01:00")
unclass(x)
[1] 3600
attr(,"tzone")
[1] "UTC"
typeof(x)
[1] "double"
attributes(x)
$class
[1] "POSIXct" "POSIXt" 

$tzone
[1] "UTC"

Demonstrate setting and retrieving time zone for date-time object

attr(x,"tzone") <- "US/Pacific"
x
[1] "1969-12-31 17:00:00 PST"
attr(x,"tzone") <- "US/Eastern"
x
[1] "1969-12-31 20:00:00 EST"

There is another type of date-times called POSIXIt. There are built on top of named lists:

y <- as.POSIXlt(x)
typeof(y)
[1] "list"
#> [1] "list"
attributes(y)
$names
 [1] "sec"    "min"    "hour"   "mday"   "mon"    "year"   "wday"   "yday"   "isdst"  "zone"   "gmtoff"

$class
[1] "POSIXlt" "POSIXt" 

$tzone
[1] "US/Eastern" "EST"        "EDT"       

$balanced
[1] TRUE

20.7.3 Tibbles

Tibbles are augmented lists: they have class “tbl_df” + “tbl” + “data.frame”, and names (column) and row.names attributes:

tb <- tibble::tibble(x = 1:5, y = 5:1)
typeof(tb)
[1] "list"
attributes(tb)
$class
[1] "tbl_df"     "tbl"        "data.frame"

$row.names
[1] 1 2 3 4 5

$names
[1] "x" "y"
df <- data.frame(x = 1:5, y = 5:1)
typeof(df)
[1] "list"
attributes(df)
$names
[1] "x" "y"

$class
[1] "data.frame"

$row.names
[1] 1 2 3 4 5

Chapter 21: Iteration

21.1.1 Prerequisites

library(tidyverse)

21.2 For loops

Imagine we have this simple tibble:

df <- tibble(
  a=rnorm(10),
  b=rnorm(10),
  c=rnorm(10),
  d=rnorm(10)
)

Calculate the median for each column in a tibble

median(df$a)
[1] -0.3157254
median(df$b)
[1] -0.8006407
median(df$c)
[1] -0.2668019
median(df$d)
[1] -0.02814063

Calculate the median for each column in the data frame ‘df’ using a for loop

df
output <- vector("double",ncol(df))
for (i in seq_along(df)){
  output[[i]] <- median(df[[i]])
}
output <- tibble(output)

Demonstrate the behavior of seq_along and length functions with an empty vector ‘y’

y <- vector("double", 0)
seq_along(y)
integer(0)
#> integer(0)
1:length(y)
[1] 1 0
#> [1] 1 0

21.3.1v Modifying an existing object

Sometimes, you want to use a for loop to modify an existing object. For example, remember our challenges from functions. We wanted to rescale every column in a data frame:

library(tidyverse)

df <- tibble(
  a=rnorm(10),
  b=rnorm(10),
  c=rnorm(10),
  d=rnorm(10)
)

rescale01 <- function(x){
  rng <- range(x,na.rm=T)
  (x-rng[1])/(rng[2]-rng[1])
}

df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)

df
for ( i in seq_along(df)){
  df[[i]] <- rescale01(df[[i]])
}

21.3.2 Looping patterns

x
[1] "1969-12-31 20:00:00 EST"
results <- vector("list",length(x))
names(results) <- names(x)

Demonstrate looping patterns using a for loop to iterate over a list ‘x’ and store results in a list ‘results’

for(i in seq_along(x)){
  name <- names(x)[[i]]
  value <- x[[i]]
}

21.3.3 Unknown output length

Create a vector ‘output’ with unknown length and store results from a for loop in it

means <- c(0,1,2)

output <- double()
for (i in seq_along(means)){
  n <- sample(100,1)
  output <- c(output,rnorm(n,means[[i]]))
}
str(output)
 num [1:223] 2.4083 1.5499 0.6081 0.0844 0.7443 ...
output
  [1]  2.40834490  1.54985893  0.60813582  0.08444820  0.74433326  0.23589873  0.13677913 -1.51138770
  [9] -1.27301392 -1.76413099 -0.63497070  1.54956856  1.50375944  0.71571312  0.57801330  0.33952611
 [17]  0.55112157  0.17114550 -0.45463725  1.16554397  0.69994812 -1.38517572 -0.21089332  0.59729886
 [25]  0.96649672  0.27565281  2.07450311  0.94767106 -1.19450592 -1.17615918 -0.06135937  0.31565475
 [33]  0.46863199 -2.44533524  1.06774440 -0.53263928  0.79354070 -1.03657232 -1.41232073 -1.32268012
 [41] -0.80619868 -1.48689463 -1.54482571  1.03872808 -1.69903338 -1.03393281  1.63922764  1.21681751
 [49] -1.50423215  0.08619177 -0.64176595 -0.43528690  2.35908464 -0.07057289  0.79716367  1.39285456
 [57]  1.99268652  1.96561675  4.02439840  2.43533373  1.71102597 -1.18110259  0.59191107  1.10348971
 [65]  0.73300412  0.12159976  0.27290089  1.61147640  0.05298932  0.28489074  0.51530326  2.48330465
 [73]  1.37412695  1.25174310  1.51181660  0.51833391  1.22731542  0.94751275  2.14534938  3.73603533
 [81]  2.50448575 -0.07692596  0.38360741  1.13672743  1.59343745  1.59795077  1.43992598  1.32878827
 [89]  4.19537063  1.71172058  1.10573970  0.60868714  1.07205788  2.30309804  0.89510610  1.53102920
 [97]  1.34831205  1.42881339  1.98257191  1.81541903  1.01326728  0.19392951  0.33627612  0.47158036
[105]  2.83906013  0.38940341  1.87692593  0.86561042  1.79769711  1.32415638  1.66865453  2.99697358
[113]  0.46228139  0.33591193  0.24631821 -0.43897896 -0.11946077  1.11951899  2.53562810  1.15236359
[121] -0.09132156 -1.16767499  0.69617045  1.53977593 -0.25021887  2.18171198  1.97165693 -0.23020504
[129] -0.44086089  1.15684137  2.24634334  1.55135055  2.31883196  0.67556263  0.08018167  1.13714558
[137]  1.91669266  1.11139134  2.44770118  0.19471109  2.17871196  4.23369053 -0.15692858  2.21704642
[145]  2.90250569 -0.47232133  1.53732998  0.24186549  2.47811777  3.33046579  2.23734182  0.96433477
[153]  2.53867102  2.31206058  1.97274207  2.13828221  2.57547609  1.13761332  1.18604257  1.61607018
[161]  2.37121047  2.84795864  3.27669950  1.60560488  2.10355804  3.13176141  3.26092255  0.75913241
[169]  0.30603003  2.52789744  0.60599780  2.73159671  0.64479899 -0.11114226  0.61712535  3.05991705
[177]  1.35211904  1.02472332  1.29546010  2.25444890  1.57803759  0.76376634 -0.73903589  2.93147906
[185]  2.07135777  1.17651876  1.52535341  1.73003413  0.57964605  1.54963744  2.16950772  1.45261534
[193]  2.78626480  2.75094794  2.99609585  2.54613765  1.71969902  4.22074008  2.97537992  1.47012235
[201]  1.27664953  2.99738151  2.29757845  1.24353502  2.34876874  0.40451196  0.98056608  2.15815198
[209]  1.21343698  2.37080467  1.56698860  3.19657275  0.47615986  0.53829438  3.06730806  1.80701539
[217]  2.43974635  1.12710760  2.72351352  2.57993773  3.26991511  1.74089217  0.42477812

Create a list ‘out’ with unknown length and store results from a for loop in it

out <- vector("list",length(means))
for (i in seq_along(means)){
  n <- sample(100,1)
  out[[i]] <- rnorm(n,means[[i]])
}
str(out)
List of 3
 $ : num [1:23] 0.109 0.669 -0.159 -0.325 -0.81 ...
 $ : num [1:97] 1.57 2.15 2.41 1.75 1.1 ...
 $ : num [1:9] 1.86 2.62 2.25 1.62 2.24 ...
str(unlist(out))
 num [1:129] 0.109 0.669 -0.159 -0.325 -0.81 ...

21.3.4 Unknown sequence length

A while loop is also more general than a for loop, because you can rewrite any for loop as a while loop, but you can’t rewrite every while loop as for loop:

for (i in seq_along(x)) {
  # body
}

# Equivalent to
i <- 1
while (i <= length(x)) {
  # body
  i <- i + 1 
}

Herhow we could use a while loop to find how many tries it takes to get three heads in a row:

flip <- function() sample(c("T", "H"), 1)

flips <- 0
nheads <- 0

while (nheads < 3) {
  if (flip() == "H") {
    nheads <- nheads + 1
  } else {
    nheads <- 0
  }
  flips <- flips + 1
}
flips
[1] 26

21.4 For loops vs. functionals

Compare for loop and functional approaches for calculating column means in a data frame

df <- tibble(
  a=rnorm(10),
  b=rnorm(10),
  c=rnorm(10),
  d=rnorm(10)
)

Using for loop

output <- vector("double",length(df))
for (i in seq_along(df)){
  output[[i]] <- mean(df[[i]])
}
output
[1] 0.476473102 0.001854536 0.558698854 0.220409290

Using functional approach with a custom function ‘col_mean’

col_mean <- function(df){
  output <- vector("double",length(df))
  for (i in seq_along(df)){
    output[i] <- mean(df[[i]])
  }
  output
}

Define a function ‘col_median’ to calculate the median for each column in the data frame ‘df’

col_median <- function(df){
  output <- vector("double",hh(df))
  for (i in seq_along(df)){
    output[i] <- median(df[[i]])
  }
  output
}

col_sd <- function(df){
  output <- vector("double",length(df))
  for (i in seq_along(df)){
    output[i] <- sd(df[[i]])
  }
  output
}

df

Define functions f1, f2, and f3 for calculating different powers of absolute deviation from the mean

f1 <- function(x) abs(x-mean(x))^1
f2 <- function(x) abs(x-mean(x))^2
f3 <- function(x) abs(x-mean(x))^3

Define a function ‘f’ to calculate the absolute deviation from the mean raised to a given power ‘i’

f <- function(x,i) abs(x-mean(x))^i

Define a function ‘col_summary’ to apply a summary function ‘fun’ to each column of the data frame ‘df’

col_summary <- function(df, fun) {
  out <- vector("double", length(df))
  for (i in seq_along(df)) {
    out[i] <- fun(df[[i]])
  }
  out
}
col_summary(df, median)
[1]  0.47327175 -0.06728873  0.35193999  0.23340748
col_summary(df, mean)
[1] 0.476473102 0.001854536 0.558698854 0.220409290

Demonstrate the use of ‘map_dbl’ from the ‘purrr’ package to apply a function to each column of the data frame ‘df’

library(purrr)
head(df)


# Reference - for loop()
output <- vector("double",length(df))
for (i in seq_along(df)){
  output[[i]] <- mean(df[[i]])
}
output
[1] 0.476473102 0.001854536 0.558698854 0.220409290
map_dbl(df,mean)
          a           b           c           d 
0.476473102 0.001854536 0.558698854 0.220409290 
map_dbl(df,median)
          a           b           c           d 
 0.47327175 -0.06728873  0.35193999  0.23340748 
map_dbl(df,sd)
        a         b         c         d 
1.1903576 0.9296956 0.7214505 0.6619177 
df %>% map_dbl(mean)
          a           b           c           d 
0.476473102 0.001854536 0.558698854 0.220409290 
df %>% map_dbl(median)
          a           b           c           d 
 0.47327175 -0.06728873  0.35193999  0.23340748 
df %>% map_dbl(sd)
        a         b         c         d 
1.1903576 0.9296956 0.7214505 0.6619177 

Demonstrate the use of ‘map_dbl’ from the ‘purrr’ package with additional arguments

map_dbl(df,mean,trim=0.5)
          a           b           c           d 
 0.47327175 -0.06728873  0.35193999  0.23340748 

Demonstrate the use of ‘map_int’ from the ‘purrr’ package to apply a function that returns integers to each element of a list

z <- list(x=1:3,y=4:5)
z
$x
[1] 1 2 3

$y
[1] 4 5
map_int(z,length)
x y 
3 2 

21.5.1 Shortcuts

Demonstrate the use of ‘safely’ from the ‘purrr’ package to create a safe version of a function

safe_log <- safely(log)
str(safe_log(10))
List of 2
 $ result: num 2.3
 $ error : NULL
str(safe_log("a"))
List of 2
 $ result: NULL
 $ error :List of 2
  ..$ message: chr "non-numeric argument to mathematical function"
  ..$ call   : language .Primitive("log")(x, base)
  ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

Demonstrate the use of ‘map’ from the ‘purrr’ package with ‘safely’ to apply a safe version of a function to each element of a list

x <- list(1,10,"a")
y <- x %>% map(safely(log))
str(y)
List of 3
 $ :List of 2
  ..$ result: num 0
  ..$ error : NULL
 $ :List of 2
  ..$ result: num 2.3
  ..$ error : NULL
 $ :List of 2
  ..$ result: NULL
  ..$ error :List of 2
  .. ..$ message: chr "non-numeric argument to mathematical function"
  .. ..$ call   : language .Primitive("log")(x, base)
  .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

Demonstrate the use of ‘transpose’ from the ‘purrr’ package to transpose a list of lists

y <- x %>% transpose()
str(y)
List of 1
 $ :List of 3
  ..$ : num 1
  ..$ : num 10
  ..$ : chr "a"

Demonstrate the use of error handling with ‘map_lgl’ and ‘is_null’ from the ‘purrr’ package

is_ok <- y$error %>% map_lgl(is_null)
x[!is_ok]
list()
# y$result[is_ok] %>% flatten_dbl()

Purrr provides two other useful adverbs:

x <- list(1,10,"a")
x %>% map_dbl(possibly(log,NA_real_))
[1] 0.000000 2.302585       NA

Demonstrate the use of ‘quietly’ from the ‘purrr’ package to suppress errors and return results with warnings

x <- list(1,-1)
x %>% map(quietly(log)) %>% str()
List of 2
 $ :List of 4
  ..$ result  : num 0
  ..$ output  : chr ""
  ..$ warnings: chr(0) 
  ..$ messages: chr(0) 
 $ :List of 4
  ..$ result  : num NaN
  ..$ output  : chr ""
  ..$ warnings: chr "NaNs produced"
  ..$ messages: chr(0) 

21.7 Mapping over multiple arguments

Generate random numbers from normal distributions with different means using ‘map’ from the ‘purrr’ package

mu <- list(5,10,-3)
mu %>% 
  map(rnorm,n=5) %>% 
  str()
List of 3
 $ : num [1:5] 5.11 4.32 5.23 5.09 4.36
 $ : num [1:5] 9.66 9.56 12.07 9.6 7.39
 $ : num [1:5] -2.57 -3.32 -4.2 -2.16 -2.32

Generate random numbers from normal distributions with different means and standard deviations using ‘map2’ from the ‘purrr’ package

sigma <- list(1,5,10)
seq_along(mu) %>% 
  map(~rnorm(5,mu[[.]],sigma[[.]])) %>% 
  str()
List of 3
 $ : num [1:5] 5.36 7.16 4.32 4.53 5.97
 $ : num [1:5] 21.09 8.26 15.76 8.76 8.33
 $ : num [1:5] -11.56 -11 -4.91 -10.91 -13.74

Define a custom ‘map2’ function to apply a binary function to corresponding elements of two lists

map2(mu,sigma,rnorm,n=5) %>% str()
List of 3
 $ : num [1:5] 4.85 4.27 4.85 3.68 3.97
 $ : num [1:5] 15.4 16.3 13.5 11.2 11.4
 $ : num [1:5] 4.561 -0.279 -11.559 0.298 -5.152
map2 <- function(x,y,f,...){
  out <- vector("list",length(x))
  for (i in seq_along(x)){
    out[[i]] <- f(x[[i]],y[[i]],...)
  }
  out
}

Apply a function to corresponding elements of multiple lists using ‘pmap’ from the ‘purrr’ package


library(magrittr)
library(purrr)

n <- list(1,3,5)
args1 <- list(n,mu,sigma)
args1 %>% 
  pmap(rnorm) %>% 
  str()
List of 3
 $ : num 3.71
 $ : num [1:3] 6.11 12.73 9.31
 $ : num [1:5] -12.562 -1.359 -0.823 -20.731 -12.676

Apply a function to corresponding elements of multiple lists with named parameters using ‘pmap’ from the ‘purrr’ package

args2 <- list(mean=mu, sd=sigma,n=n)
args2 %>% 
  pmap(rnorm) %>% 
  str()
List of 3
 $ : num 4.34
 $ : num [1:3] 9.78 8.19 11.21
 $ : num [1:5] 0.742 6.791 14.991 -1.91 0.511

Apply a function to corresponding rows of a data frame using ‘pmap’ from the ‘purrr’ package with a tibble

library(tidyverse)
parms <- tribble(
  ~mean,~sd,~n,
  5,1,1,
  10,5,3,
  -3,10,5
)

parms %>% 
  pmap(rnorm)
[[1]]
[1] 3.243648

[[2]]
[1]  9.605242 11.025883  6.007252

[[3]]
[1]  16.669768   5.855557 -12.841618  -3.360844   5.139707

21.7.1 Involing different functions

Invoke different functions with different parameters using ‘invoke_map’ from the ‘purrr’ package

f <- c("runif","rnorm","rpois")
param <- list(
  list(min=-1,max=1),
  list(sd=5),
  list(lambda=10)
)

f
[1] "runif" "rnorm" "rpois"
param
[[1]]
[[1]]$min
[1] -1

[[1]]$max
[1] 1


[[2]]
[[2]]$sd
[1] 5


[[3]]
[[3]]$lambda
[1] 10

To handle this case, you can use invoke_map():

invoke_map(f,param,n=5) %>% 
  str()
Warning: `invoke_map()` was deprecated in purrr 1.0.0.
Please use map() + exec() instead.
List of 3
 $ : num [1:5] 0.7386 -0.9649 -0.0834 -0.4986 -0.9628
 $ : num [1:5] 6.447 -6.531 -0.428 6.091 8.155
 $ : int [1:5] 9 11 9 9 11

Invoke different functions with different parameters using ‘pmap’ from the ‘purrr’ package and a tibble

sim <- tribble(
  ~f,      ~params,
  "runif", list(min = -1, max = 1),
  "rnorm", list(sd = 5),
  "rpois", list(lambda = 10)
)
sim %>% 
  mutate(sim = invoke_map(f, params, n = 10))

21.8 Walk

Perform side effects without returning a value for each element of a list using ‘walk’ from the ‘purrr’ package

x <- list(1,"a",3)
x %>% 
  walk(print)
[1] 1
[1] "a"
[1] 3

Perform side effects on each element of a list using ‘walk’ from the ‘purrr’ package, then save the results

library(ggplot2)
plots <- mtcars %>% 
  split(.$cyl) %>% 
  map(~ggplot(., aes(mpg, wt)) + geom_point())
paths <- stringr::str_c(names(plots), ".pdf")

pwalk(list(paths, plots), ggsave, path = tempdir())
Saving 7 x 7 in image

Retain or remove elements of a list based on a predicate function using ‘keep’ and ‘discard’ from the ‘purrr’ package

iris %>% 
  keep(is.factor) %>% 
  str()
'data.frame':   150 obs. of  1 variable:
 $ Species: Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
iris %>% 
  discard(is.factor) %>%
  str()
'data.frame':   150 obs. of  4 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
library(tidyverse)
library(magrittr)

21.9.2 Reduce and accumulate

Iteratively combine elements of a list using a binary function with ‘reduce’ from the ‘purrr’ package

dfs <- list(
  age=tibble(name="John",age=30),
  sex=tibble(name=c("John","Mary"),sex=c("M","F")),
  trt=tibble(name="Mary",treatment="A")
)

dfs %>% reduce(full_join)
Joining with `by = join_by(name)`Joining with `by = join_by(name)`

Find the intersection of multiple vectors using ‘reduce’ from the ‘purrr’ package

vs <- list(
  c(1,3,5,6,10),
  c(1,2,3,7,8,10),
  c(1,2,3,4,8,9,10)
)
vs %>% reduce(intersect)
[1]  1  3 10

Iteratively apply a function to elements of a list using ‘accumulate’ from the ‘purrr’ package

x <- sample(10)
x
 [1]  7  3  4  2  8  9  1  5 10  6
x %>% accumulate(`+`)
 [1]  7 10 14 16 24 33 34 39 49 55
LS0tDQp0aXRsZTogIkludHJvZHVjdGlvbiB0byBSIg0KYXV0aG9yOiAiQmliZWsgU2Fwa290YSINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQojIFRpYmJsZXMNCg0KVGFzayAxOkxvYWRpbmcgdGhlIHRpZHl2ZXJzZSBwYWNrYWdlLg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KVGFzayAyOkNvbnZlcnRpbmcgdGhlIGlyaXMgZGF0YXNldCB0byBhIHRpYmJsZS4NCmBgYHtyfQ0KYXNfdGliYmxlKGlyaXMpDQpgYGANClRhc2sgMzogQ3JlYXRpbmcgYSB0aWJibGUgd2l0aCBjb2x1bW5zICJ4LCIgInksIiBhbmQgInosIiB3aGVyZSAieCIgcmFuZ2VzIGZyb20gMSB0byA1LCAieSIgaXMgMSBmb3IgYWxsIHJvd3MsIGFuZCAieiIgaXMgY2FsY3VsYXRlZCBhcyB0aGUgc3F1YXJlIG9mICJ4IiBwbHVzICJ5IiBmb3IgZWFjaCByb3cuDQpgYGB7cn0NCnRpYmJsZSgNCiAgeCA9IDE6NSwgDQogIHkgPSAxLCANCiAgeiA9IHggXiAyICsgeQ0KKQ0KYGBgDQoNClRhc2sgNDpDcmVhdGluZyBhIHRpYmJsZSB3aXRoIGNvbHVtbnMgbmFtZWQgIjopIiAocmVwcmVzZW50aW5nICJzbWlsZSIpLCAiICIgKHJlcHJlc2VudGluZyAic3BhY2UiKSwgYW5kICIyMDAwIiAocmVwcmVzZW50aW5nICJudW1iZXIiKS4NCmBgYHtyfQ0KdGIgPC0gdGliYmxlKA0KICBgOilgID0gInNtaWxlIiwgDQogIGAgYCA9ICJzcGFjZSIsDQogIGAyMDAwYCA9ICJudW1iZXIiDQopDQp0Yg0KYGBgDQoNClRhc2sgNTpDcmVhdGluZyBhIHRpYmJsZSB3aXRoIGNvbHVtbnMgIngsIiAieSwiIGFuZCAieiwiIGNvbnRhaW5pbmcgdGhlIHZhbHVlcyAiYSwiIDIsIDMuNiBhbmQgImIsIiAxLCA4LjUgcmVzcGVjdGl2ZWx5Lg0KDQpgYGB7cn0NCnRyaWJibGUoDQogIH54LCB+eSwgfnosDQogIA0KICAiYSIsIDIsIDMuNiwNCiAgImIiLCAxLCA4LjUNCikNCmBgYA0KDQojIFRpYmJsZXMgdnMuIGRhdGEuZnJhbWUNCg0KVGFzay0xOkNyZWF0aW5nIGEgdGliYmxlIHdpdGggY29sdW1ucyAiYSwiICJiLCIgImMsIiAiZCwiIGFuZCAiZSwiIGNvbnRhaW5pbmcgMTAwMCByYW5kb21seSBnZW5lcmF0ZWQgdmFsdWVzIGZvciBlYWNoIGNvbHVtbiwgcmVwcmVzZW50aW5nIGRhdGVzLCBudW1iZXJzLCBhbmQgbGV0dGVycy4NCg0KYGBge3J9DQp0aWJibGUoDQogIGEgPSBsdWJyaWRhdGU6Om5vdygpICsgcnVuaWYoMWUzKSAqIDg2NDAwLA0KICBiID0gbHVicmlkYXRlOjp0b2RheSgpICsgcnVuaWYoMWUzKSAqIDMwLA0KICBjID0gMToxZTMsDQogIGQgPSBydW5pZigxZTMpLA0KICBlID0gc2FtcGxlKGxldHRlcnMsIDFlMywgcmVwbGFjZSA9IFRSVUUpDQopDQpgYGANClRhc2sgMjogVG5zdGFsbGluZyB0aGUgcGFja2FnZQ0KYGBge3J9DQpwYWNrYWdlX3RvX2luc3RhbGwgPC0gYygibnljZmxpZ2h0czEzIikNCg0KZm9yIChwYWNrYWdlX25hbWUgaW4gcGFja2FnZV90b19pbnN0YWxsKSB7DQogIGlmICghcmVxdWlyZU5hbWVzcGFjZShwYWNrYWdlX25hbWUsIHF1aWV0bHkgPSBUUlVFKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMocGFja2FnZV9uYW1lKQ0KICB9DQp9DQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmBgYA0KDQpUYXNrIDM6IFByaW50aW5nIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSBueWNmbGlnaHRzMTM6OmZsaWdodHMgZGF0YXNldCB3aXRoIHVubGltaXRlZCB3aWR0aC4NCmBgYHtyfQ0KbnljZmxpZ2h0czEzOjpmbGlnaHRzICU+JSANCiAgcHJpbnQobiA9IDEwLCB3aWR0aCA9IEluZikNCmBgYA0KDQoNClRhc2sgNDogVmlld2luZyB0aGUgbnljZmxpZ2h0czEzOjpmbGlnaHRzIGRhdGFzZXQgaW4gYSBzZXBhcmF0ZSB3aW5kb3cgZm9yIGludGVyYWN0aXZlIGV4cGxvcmF0aW9uLg0KYGBge3J9DQpueWNmbGlnaHRzMTM6OmZsaWdodHMgJT4lIA0KICBWaWV3KCkNCmBgYA0KDQojIyBTdWJzZXR0aW5nDQoNClRhc2sgMTogQ3JlYXRpbmcgYSB0aWJibGUgbmFtZWQgImRmIiB3aXRoIGNvbHVtbnMgIngiIGFuZCAieSwiIHRoZW4gYWNjZXNzaW5nIHRoZSAieCIgY29sdW1uIHVzaW5nIGRpZmZlcmVudCBtZXRob2RzOg0KYGBge3J9DQpkZiA8LSB0aWJibGUoDQogIHggPSBydW5pZig1KSwjZnVuY3Rpb24gdGhhdCBnZW5lcmF0ZXMgcmFuZG9tIG51bWJlcnMgZnJvbSBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uDQogIHkgPSBybm9ybSg1KSAjIGZ1bmN0aW9uIHRoYXQgZ2VuZXJhdGVzIHJhbmRvbSBudW1iZXJzIGZyb20gYSBub3JtYWwgKEdhdXNzaWFuKSBkaXN0cmlidXRpb24NCikNCg0KZGYkeA0KDQpkZltbIngiXV0NCg0KZGZbWzFdXQ0KDQpkZiAlPiUgLiR4DQoNCg0KYGBgDQojIyBJbnRlcmFjdGluZyB3aXRoIG9sZGVyIGNvZGUNClRhc2stMTogRGV0ZXJtaW5pbmcgdGhlIGNsYXNzIG9mIHRoZSBvYmplY3QgInRiIiBhZnRlciBjb252ZXJ0aW5nIGl0IHRvIGEgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQpjbGFzcyhhcy5kYXRhLmZyYW1lKHRiKSkNCmBgYA0KIyMgRXhlcmNpc2VzDQpUYXNrLTE6IEhvdyBjYW4geW91IHRlbGwgaWYgYW4gb2JqZWN0IGlzIGEgdGliYmxlPyAoSGludDogdHJ5IHByaW50aW5nIG10Y2Fycywgd2hpY2ggaXMgYSByZWd1bGFyIGRhdGEgZnJhbWUpLg0KYGBge3J9DQptdGNhcnMNCmBgYA0KVGFzay0yDQpgYGB7cn0NCiMgSW4gYSBkYXRhLmZyYW1lLCBleHRyYWN0aW5nIGEgbm9uLWV4aXN0ZW50IGNvbHVtbiByZXR1cm5zIE5VTEwsDQojIHdoZXJlYXMgaW4gYSB0aWJibGUsIGl0IHJhaXNlcyBhbiBlcnJvciwgcHJvdmlkaW5nIGltbWVkaWF0ZSBmZWVkYmFjay4NCiMgT3RoZXIgb3BlcmF0aW9ucywgc3VjaCBhcyBleHRyYWN0aW5nIGV4aXN0aW5nIGNvbHVtbnMgYW5kIHN1YnNldHMgb2YgY29sdW1ucywNCiMgYmVoYXZlIHNpbWlsYXJseSBhY3Jvc3MgYm90aCBkYXRhIGZyYW1lcyBhbmQgdGliYmxlcy4NCiMgVGhlIGRlZmF1bHQgYmVoYXZpb3Igb2YgZGF0YS5mcmFtZXMgbWF5IGxlYWQgdG8gZnJ1c3RyYXRpb24NCiMgZHVlIHRvIHRoZSBsYWNrIG9mIGVycm9yIGZlZWRiYWNrIGZvciBub24tZXhpc3RlbnQgY29sdW1ucywNCiMgcG90ZW50aWFsbHkgY2F1c2luZyB1bm5vdGljZWQgbWlzdGFrZXMgYW5kIGRpZmZpY3VsdHkgaW4gZGVidWdnaW5nLg0KIyBJbiBjb250cmFzdCwgdGliYmxlcyBvZmZlciBtb3JlIHJvYnVzdCBiZWhhdmlvciwgZW5oYW5jaW5nIGRhdGEgaW50ZWdyaXR5DQojIGFuZCBkZWJ1Z2dpbmcgZWZmaWNpZW5jeS4NCg0KZGYgPC0gZGF0YS5mcmFtZShhYmMgPSAxLCB4eXogPSAiYSIpDQoNCiMgRXh0cmFjdGluZyBub24tZXhpc3RlbnQgY29sdW1uIGluIGEgZGF0YS5mcmFtZQ0KZGYkeCAgIyBSZXR1cm5zIE5VTEwNCg0KIyBFeHRyYWN0aW5nIGV4aXN0aW5nIGNvbHVtbiBpbiBhIGRhdGEuZnJhbWUNCmRmWywgInh5eiJdICAjIFJldHVybnMgYSBkYXRhIGZyYW1lIHdpdGggb25lIGNvbHVtbiBjb250YWluaW5nIHRoZSB2YWx1ZXMgb2YgdGhlICJ4eXoiIGNvbHVtbg0KDQojIEV4dHJhY3RpbmcgbXVsdGlwbGUgY29sdW1ucyBpbiBhIGRhdGEuZnJhbWUNCmRmWywgYygiYWJjIiwgInh5eiIpXSAgIyBSZXR1cm5zIGEgZGF0YSBmcmFtZSBjb250YWluaW5nIG9ubHkgdGhlIHNwZWNpZmllZCBjb2x1bW5zDQoNCmBgYA0KVGFzay0zOklmIHlvdSBoYXZlIHRoZSBuYW1lIG9mIGEgdmFyaWFibGUgc3RvcmVkIGluIGFuIG9iamVjdCwgZS5nLiB2YXIgPC0gIm1wZyIsIGhvdyBjYW4geW91IGV4dHJhY3QgdGhlIHJlZmVyZW5jZSB2YXJpYWJsZSBmcm9tIGEgdGliYmxlPw0KDQoNCiMgTm8gcGFjYWthZ2VzDQpgYGB7cn0NCiMgaGVpZ2h0cyA8LSByZWFkX2NzdigiZGF0YS9oZWlnaHRzLmNzdiIpDQpgYGANCg0KVGFzayAxOiAgbGlzdGluZyBzZXZlcmFsIHRhYmxlczogdGFibGUxLCB0YWJsZTIsIHRhYmxlMywgdGFibGU0YSwgYW5kIHRhYmxlNGIuDQpgYGB7cn0NCnRhYmxlMQ0KdGFibGUyDQp0YWJsZTMNCnRhYmxlNGENCnRhYmxlNGINCmBgYA0KVGFzayAyOiBDYWxjdWxhdGluZyB0aGUgcmF0ZSBieSBkaXZpZGluZyB0aGUgbnVtYmVyIG9mIGNhc2VzIGJ5IHRoZSBwb3B1bGF0aW9uIGFuZCB0aGVuIG11bHRpcGx5aW5nIGJ5IDEwLDAwMCBmb3IgdGFibGUxLg0KYGBge3J9DQp0YWJsZTEgJT4lIA0KICBtdXRhdGUocmF0ZSA9IGNhc2VzIC8gcG9wdWxhdGlvbiAqIDEwMDAwKQ0KYGBgDQpUYXNrIDM6IENvdW50aW5nIHRoZSBvY2N1cnJlbmNlcyBvZiBlYWNoIHllYXIgaW4gdGFibGUxLCB1c2luZyB0aGUgJ2Nhc2VzJyBjb2x1bW4gYXMgdGhlIHdlaWdodC4NCmBgYHtyfQ0KdGFibGUxICU+JSANCiAgY291bnQoeWVhciwgd3QgPSBjYXNlcykNCmBgYA0KVGFzayA0OiBDcmVhdGluZyBhIGdncGxvdCB1c2luZyB0YWJsZTEsIHBsb3R0aW5nICd5ZWFyJyBhZ2FpbnN0ICdjYXNlcycgd2l0aCBsaW5lcyBncm91cGVkIGJ5ICdjb3VudHJ5JyBhbmQgY29sb3JlZCBpbiBncmV5NTAsIGFsb25nIHdpdGggcG9pbnRzIGNvbG9yZWQgYnkgJ2NvdW50cnknLg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdCh0YWJsZTEsIGFlcyh5ZWFyLCBjYXNlcykpICsgDQogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBjb3VudHJ5KSwgY29sb3VyID0gImdyZXk1MCIpICsgDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGNvdW50cnkpKQ0KYGBgDQojICBQaXZvdGluZw0KIyMgTG9uZ2VyDQpUYXNrLTE6IHJlZmVycmluZyB0byAndGFibGU0YScNCmBgYHtyfQ0KdGFibGU0YQ0KYGBgDQpUYXNrLTI6IFJlc2hhcGluZyB0YWJsZTRhIHVzaW5nIHBpdm90X2xvbmdlciBmb3IgY29sdW1ucyAnMTk5OScgYW5kICcyMDAwJyBpbnRvICd5ZWFyJyBhbmQgJ2Nhc2VzJy4NCmBgYHtyfQ0KdGFibGU0YSAlPiUgDQogIHBpdm90X2xvbmdlcihjKGAxOTk5YCwgYDIwMDBgKSwgbmFtZXNfdG8gPSAieWVhciIsIHZhbHVlc190byA9ICJjYXNlcyIpDQpgYGANClRhc2stMzogUmVzaGFwaW5nIHRhYmxlNGIgd2l0aCBwaXZvdF9sb25nZXIgZm9yIGNvbHVtbnMgJzE5OTknIGFuZCAnMjAwMCcgaW50byAneWVhcicgYW5kICdwb3B1bGF0aW9uJy4NCmBgYHtyfQ0KdGFibGU0YiAlPiUgDQogIHBpdm90X2xvbmdlcihjKGAxOTk5YCwgYDIwMDBgKSwgbmFtZXNfdG8gPSAieWVhciIsIHZhbHVlc190byA9ICJwb3B1bGF0aW9uIikgICNmdW5jdGlvbiB0cmFuc2Zvcm1zIHdpZGUgZGF0YSBpbnRvIGxvbmcgZm9ybWF0IGJ5IHN0YWNraW5nIG11bHRpcGxlIGNvbHVtbnMgaW50byB0d286IG9uZSBmb3IgdmFyaWFibGUgbmFtZXMgYW5kIG9uZSBmb3IgdGhlaXIgY29ycmVzcG9uZGluZyB2YWx1ZXMNCmBgYA0KVGFzay00OiBjcmVhdGluZyB0aWR5IGRhdGFzZXRzIHRpZHk0YSBhbmQgdGlkeTRiIGJ5IHVzaW5nIHBpdm90X2xvbmdlciBvbiB0YWJsZTRhIGFuZCB0YWJsZTRiIHRvIHJlc2hhcGUgdGhlbS4gVGhlbiwgcGVyZm9ybWluZyBhIGxlZnQgam9pbiBvbiB0aWR5NGEgYW5kIHRpZHk0Yi4NCmBgYHtyfQ0KdGlkeTRhIDwtIHRhYmxlNGEgJT4lIA0KICBwaXZvdF9sb25nZXIoYyhgMTk5OWAsIGAyMDAwYCksIG5hbWVzX3RvID0gInllYXIiLCB2YWx1ZXNfdG8gPSAiY2FzZXMiKQ0KdGlkeTRiIDwtIHRhYmxlNGIgJT4lIA0KICBwaXZvdF9sb25nZXIoYyhgMTk5OWAsIGAyMDAwYCksIG5hbWVzX3RvID0gInllYXIiLCB2YWx1ZXNfdG8gPSAicG9wdWxhdGlvbiIpDQpsZWZ0X2pvaW4odGlkeTRhLCB0aWR5NGIpDQpgYGANCiMjIFdpZGVyDQpUYXNrLTE6RGlzcGxheWluZyB0YWJsZSAyDQpgYGB7cn0NCnRhYmxlMg0KYGBgDQoNClRhc2stMjogdXNpbmcgdGhlIHBpdm90X3dpZGVyIGZ1bmN0aW9uIG9uIHRhYmxlMiB0byB0cmFuc2Zvcm0gaXQgZnJvbSBsb25nIHRvIHdpZGUgZm9ybWF0LCB3aXRoICd0eXBlJyBiZWNvbWluZyB0aGUgbmV3IGNvbHVtbiBuYW1lcyBhbmQgJ2NvdW50JyBiZWluZyB0aGUgY29ycmVzcG9uZGluZyB2YWx1ZXMuDQpgYGB7cn0NCnRhYmxlMiAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdHlwZSwgdmFsdWVzX2Zyb20gPSBjb3VudCkNCmBgYA0KIyMjIyMjIyMjIyMNCiMgU2VwYXJhdGluZyBhbmQgdW5pdGluZw0KIyMgU2VwYXJhdGUNClRhc2stMTpkaXNwbGF5aW5nIHRhYmxlMw0KYGBge3J9DQogdGFibGUzDQpgYGANCg0KVGFzay0yOiBVc2luZyB0aGUgc2VwYXJhdGUgZnVuY3Rpb24gb24gdGFibGUzIHNwbGl0cyB0aGUgJ3JhdGUnIGNvbHVtbiBpbnRvIHR3byBzZXBhcmF0ZSBjb2x1bW5zIG5hbWVkICdjYXNlcycgYW5kICdwb3B1bGF0aW9uJy4NCmBgYHtyfQ0KdGFibGUzICU+JSANCiAgc2VwYXJhdGUocmF0ZSwgaW50byA9IGMoImNhc2VzIiwgInBvcHVsYXRpb24iKSkNCmBgYA0KVGFzay0zOlVzaW5nIHRoZSBzZXBhcmF0ZSBmdW5jdGlvbiBvbiB0YWJsZTMgc3BsaXRzIHRoZSAncmF0ZScgY29sdW1uIGludG8gdHdvIHNlcGFyYXRlIGNvbHVtbnMgbmFtZWQgJ2Nhc2VzJyBhbmQgJ3BvcHVsYXRpb24nLCB1c2luZyB0aGUgJy8nIGNoYXJhY3RlciBhcyB0aGUgc2VwYXJhdG9yLg0KYGBge3J9DQp0YWJsZTMgJT4lIA0KICBzZXBhcmF0ZShyYXRlLCBpbnRvID0gYygiY2FzZXMiLCAicG9wdWxhdGlvbiIpLCBzZXAgPSAiLyIpDQpgYGANClRhc2stNDpVc2luZyB0aGUgc2VwYXJhdGUgZnVuY3Rpb24gb24gdGFibGUzIHNwbGl0cyB0aGUgJ3JhdGUnIGNvbHVtbiBpbnRvIHR3byBzZXBhcmF0ZSBjb2x1bW5zIG5hbWVkICdjYXNlcycgYW5kICdwb3B1bGF0aW9uJywgY29udmVydGluZyB0aGUgcmVzdWx0aW5nIGNvbHVtbnMgdG8gdGhlaXIgYXBwcm9wcmlhdGUgZGF0YSB0eXBlcy4NCmBgYHtyfQ0KdGFibGUzICU+JSANCiAgc2VwYXJhdGUocmF0ZSwgaW50byA9IGMoImNhc2VzIiwgInBvcHVsYXRpb24iKSwgY29udmVydCA9IFRSVUUpDQpgYGANClRhc2stNTogIEFwcGx5aW5nIHRoZSBzZXBhcmF0ZSBmdW5jdGlvbiB0byB0YWJsZTMsIHRoZSAneWVhcicgY29sdW1uIGlzIGRpdmlkZWQgaW50byB0d28gc2VwYXJhdGUgY29sdW1ucyBsYWJlbGVkICdjZW50dXJ5JyBhbmQgJ3llYXInLCB3aXRoIHRoZSBzZXBhcmF0b3IgZGVmaW5lZCBhcyB0aGUgc2Vjb25kIGNoYXJhY3Rlci4NCmBgYHtyfQ0KdGFibGUzICU+JSANCiAgc2VwYXJhdGUoeWVhciwgaW50byA9IGMoImNlbnR1cnkiLCAieWVhciIpLCBzZXAgPSAyKQ0KYGBgDQojIyBVbml0ZQ0KDQpUYXNrLTE6IFRoZSB1bml0ZSBmdW5jdGlvbiBpcyBhcHBsaWVkIHRvIHRhYmxlNSB0byBtZXJnZSB0aGUgJ2NlbnR1cnknIGFuZCAneWVhcicgY29sdW1ucyBpbnRvIGEgc2luZ2xlIGNvbHVtbiBuYW1lZCAnbmV3Jy4NCmBgYHtyfQ0KdGFibGU1ICU+JSANCiAgdW5pdGUobmV3LCBjZW50dXJ5LCB5ZWFyKQ0KYGBgDQpUYXNrLTI6IHVuaXRlIGZ1bmN0aW9uIGlzIGFwcGxpZWQgdG8gdGFibGU1IHRvIG1lcmdlIHRoZSAnY2VudHVyeScgYW5kICd5ZWFyJyBjb2x1bW5zIGludG8gYSBzaW5nbGUgY29sdW1uIG5hbWVkICduZXcnLCB3aXRoIG5vIHNlcGFyYXRvciBiZXR3ZWVuIHRoZW0uDQpgYGB7cn0NCnRhYmxlNSAlPiUgDQogIHVuaXRlKG5ldywgY2VudHVyeSwgeWVhciwgc2VwID0gIiIpDQpgYGANCiMgIE1pc3NpbmcgdmFsdWVzDQpUYXNrLTE6IENyZWF0ZSBhIHRpYmJsZSBuYW1lZCAic3RvY2tzIiB3aXRoIGNvbHVtbnMgInllYXIiLCAicXRyIiAocXVhcnRlciksIGFuZCAicmV0dXJuIiwgaGF2aW5nIGRhdGEgZm9yIDIwMTUgYW5kIDIwMTYsIHdpdGggcXVhcnRlcmx5IHJldHVybnMgc3BlY2lmaWVkIGFuZCBzb21lIG1pc3NpbmcgZW50cmllcyBhcyBOQS4NCmBgYHtyfQ0Kc3RvY2tzIDwtIHRpYmJsZSgNCiAgeWVhciAgID0gYygyMDE1LCAyMDE1LCAyMDE1LCAyMDE1LCAyMDE2LCAyMDE2LCAyMDE2KSwNCiAgcXRyICAgID0gYyggICAxLCAgICAyLCAgICAzLCAgICA0LCAgICAyLCAgICAzLCAgICA0KSwNCiAgcmV0dXJuID0gYygxLjg4LCAwLjU5LCAwLjM1LCAgIE5BLCAwLjkyLCAwLjE3LCAyLjY2KQ0KKQ0KYGBgDQoNCg0KVGFzay0yOlBpdm90aW5nIHRoZSAic3RvY2tzIiB0aWJibGUgdG8gd2lkZW4gdGhlIGRhdGEsIGV4dHJhY3RpbmcgY29sdW1ucyBmcm9tIHRoZSAieWVhciIgdmFyaWFibGUgYW5kIHZhbHVlcyBmcm9tIHRoZSAicmV0dXJuIiB2YXJpYWJsZS4NCmBgYHtyfQ0Kc3RvY2tzICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHllYXIsIHZhbHVlc19mcm9tID0gcmV0dXJuKQ0KYGBgDQoNClRhc2stMzogcGl2b3QgdGhlIGRhdGEgdG8gYSB3aWRlIGZvcm1hdCB3aXRoIGNvbHVtbnMgZm9yIGVhY2ggeWVhcidzIHJldHVybnMsIHRoZW4gcmVzaGFwZSBpdCBiYWNrIHRvIGEgbG9uZyBmb3JtYXQsIGtlZXBpbmcgb25seSB0aGUgbm9uLW1pc3NpbmcgdmFsdWVzIGluIHRoZSAicmV0dXJuIiBjb2x1bW4uDQpgYGB7cn0NCnN0b2NrcyAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB5ZWFyLCB2YWx1ZXNfZnJvbSA9IHJldHVybikgJT4lIA0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IGMoYDIwMTVgLCBgMjAxNmApLCANCiAgICBuYW1lc190byA9ICJ5ZWFyIiwgDQogICAgdmFsdWVzX3RvID0gInJldHVybiIsIA0KICAgIHZhbHVlc19kcm9wX25hID0gVFJVRQ0KICApDQpgYGANClRhc2stNDpGaWxsaW5nIG1pc3NpbmcgY29tYmluYXRpb25zIG9mICJ5ZWFyIiBhbmQgInF0ciIgaW4gdGhlICJzdG9ja3MiIGRhdGFzZXQuDQpgYGB7cn0NCnN0b2NrcyAlPiUgDQogIGNvbXBsZXRlKHllYXIsIHF0cikNCmBgYA0KVGFzay01OkNyZWF0aW5nIGEgdGliYmxlIG5hbWVkICJ0cmVhdG1lbnQiIGNvbnRhaW5pbmcgaW5mb3JtYXRpb24gYWJvdXQgaW5kaXZpZHVhbHMsIHRoZWlyIHRyZWF0bWVudCBncm91cHMsIGFuZCB0aGVpciByZXNwb25zZXMsIHdpdGggc29tZSBtaXNzaW5nIHZhbHVlcyBmb3IgdGhlICJwZXJzb24iIGNvbHVtbi4NCmBgYHtyfQ0KdHJlYXRtZW50IDwtIHRyaWJibGUoDQogIH4gcGVyc29uLCAgICAgICAgICAgfiB0cmVhdG1lbnQsIH5yZXNwb25zZSwNCiAgIkRlcnJpY2sgV2hpdG1vcmUiLCAxLCAgICAgICAgICAgNywNCiAgTkEsICAgICAgICAgICAgICAgICAyLCAgICAgICAgICAgMTAsDQogIE5BLCAgICAgICAgICAgICAgICAgMywgICAgICAgICAgIDksDQogICJLYXRoZXJpbmUgQnVya2UiLCAgMSwgICAgICAgICAgIDQNCikNCmBgYA0KVGFzay02OiBGaWxsaW5nIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgInBlcnNvbiIgY29sdW1uIG9mIHRoZSAidHJlYXRtZW50IiB0aWJibGUuDQpgYGB7cn0NCnRyZWF0bWVudCAlPiUgDQogIGZpbGwocGVyc29uKQ0KDQpgYGANCg0KIyBDYXNlIFN0dWR5DQpUYXNrLTE6IExvYWRpbmcgZGF0YSBzZXQNCmBgYHtyfQ0Kd2hvDQpgYGANCg0KVGFzay0yOlBpdm90aW5nIHRoZSAid2hvIiBkYXRhc2V0IGZyb20gd2lkZSB0byBsb25nIGZvcm1hdCwgY29uZGVuc2luZyBjb2x1bW5zIGludG8gImNhc2VzIiBhbmQgY2FwdHVyaW5nIHRoZSBvcmlnaW5hbCBjb2x1bW4gbmFtZXMgaW4gImtleSIuDQpgYGB7cn0NCndobzEgPC0gd2hvICU+JSANCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGNvbHMgPSBuZXdfc3BfbTAxNDpuZXdyZWxfZjY1LCANCiAgICBuYW1lc190byA9ICJrZXkiLCANCiAgICB2YWx1ZXNfdG8gPSAiY2FzZXMiLCANCiAgICB2YWx1ZXNfZHJvcF9uYSA9IFRSVUUNCiAgKQ0Kd2hvMQ0KYGBgDQpUYXNrLTM6Q291bnRpbmcgdGhlIG9jY3VycmVuY2VzIG9mIGVhY2ggImtleSIgaW4gdGhlICJ3aG8xIiBkYXRhc2V0Lg0KYGBge3J9DQogIHdobzEgJT4lIA0KICAgIGNvdW50KGtleSkNCmBgYA0KVGFzay00OlJlcGxhY2luZyAibmV3cmVsIiB3aXRoICJuZXdfcmVsIiBpbiB0aGUgImtleSIgY29sdW1uIG9mIHRoZSAid2hvMSIgZGF0YXNldCB0byBjcmVhdGUgIndobzIuIg0KYGBge3J9DQp3aG8yIDwtIHdobzEgJT4lIA0KICBtdXRhdGUoa2V5ID0gc3RyaW5ncjo6c3RyX3JlcGxhY2Uoa2V5LCAibmV3cmVsIiwgIm5ld19yZWwiKSkNCndobzINCmBgYA0KVGFzay01OlNlcGFyYXRpbmcgdGhlICJrZXkiIGNvbHVtbiBpbiB0aGUgIndobzIiIGRhdGFzZXQgaW50byAibmV3LCIgInR5cGUsIiBhbmQgInNleGFnZSIgY29sdW1ucyB1c2luZyAiXyIgYXMgdGhlIHNlcGFyYXRvciB0byBjcmVhdGUgIndobzMuIg0KYGBge3J9DQp3aG8zIDwtIHdobzIgJT4lIA0KICBzZXBhcmF0ZShrZXksIGMoIm5ldyIsICJ0eXBlIiwgInNleGFnZSIpLCBzZXAgPSAiXyIpDQp3aG8zDQpgYGANClRhc2stNjpDb3VudGluZyB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCB1bmlxdWUgdmFsdWUgaW4gdGhlICJuZXciIGNvbHVtbiBvZiB0aGUgIndobzMiIGRhdGFzZXQuDQpgYGB7cn0NCndobzMgJT4lIA0KICBjb3VudChuZXcpDQpgYGANCg0KVGFzay03OlJlbW92aW5nIHRoZSAibmV3IiwgImlzbzIiLCBhbmQgImlzbzMiIGNvbHVtbnMgZnJvbSB0aGUgIndobzMiIGRhdGFzZXQgYW5kIGFzc2lnbmluZyB0aGUgcmVzdWx0IHRvICJ3aG80Ii4NCmBgYHtyfQ0Kd2hvNCA8LSB3aG8zICU+JSANCiAgc2VsZWN0KC1uZXcsIC1pc28yLCAtaXNvMykNCmBgYA0KDQpUYXNrLTg6U3BsaXR0aW5nIHRoZSAic2V4YWdlIiBjb2x1bW4gb2YgdGhlICJ3aG80IiBkYXRhc2V0IGludG8gInNleCIgYW5kICJhZ2UiIGNvbHVtbnMsIHNlcGFyYXRlZCBieSB0aGUgZmlyc3QgY2hhcmFjdGVyLCBhbmQgYXNzaWduaW5nIHRoZSByZXN1bHQgdG8gIndobzUiLg0KYGBge3J9DQp3aG81IDwtIHdobzQgJT4lIA0KICBzZXBhcmF0ZShzZXhhZ2UsIGMoInNleCIsICJhZ2UiKSwgc2VwID0gMSkNCndobzUNCmBgYA0KVGFzay05OlRyYW5zZm9ybWluZyB0aGUgIndobyIgZGF0YXNldCBmcm9tIHdpZGUgdG8gbG9uZyBmb3JtYXQsIGFkanVzdGluZyBjb2x1bW4gbmFtZXMsIGV4dHJhY3RpbmcgbWVhbmluZ2Z1bCB2YXJpYWJsZXMsIGRyb3BwaW5nIHVubmVjZXNzYXJ5IGNvbHVtbnMsIGFuZCBzcGxpdHRpbmcgdGhlICJzZXhhZ2UiIGNvbHVtbiBpbnRvICJzZXgiIGFuZCAiYWdlIi4NCmBgYHtyfQ0Kd2hvICU+JQ0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IG5ld19zcF9tMDE0Om5ld3JlbF9mNjUsIA0KICAgIG5hbWVzX3RvID0gImtleSIsIA0KICAgIHZhbHVlc190byA9ICJjYXNlcyIsIA0KICAgIHZhbHVlc19kcm9wX25hID0gVFJVRQ0KICApICU+JSANCiAgbXV0YXRlKA0KICAgIGtleSA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlKGtleSwgIm5ld3JlbCIsICJuZXdfcmVsIikNCiAgKSAlPiUNCiAgc2VwYXJhdGUoa2V5LCBjKCJuZXciLCAidmFyIiwgInNleGFnZSIpKSAlPiUgDQogIHNlbGVjdCgtbmV3LCAtaXNvMiwgLWlzbzMpICU+JSANCiAgc2VwYXJhdGUoc2V4YWdlLCBjKCJzZXgiLCAiYWdlIiksIHNlcCA9IDEpDQoNCmBgYA0KDQoNCiMjIENILTEzOiBSZWxhdGlvbmFsIGRhdGENCg0KVGFzay0xOkxvZGluZyB0aGUgbGlicmFyaWVzDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpgYGANCg0KIyMgbnljZmxpZ2h0czEzDQpUYXNrLTE6IGFpcmxpbmVzIGRhdGENCmBgYHtyfQ0KYWlybGluZXMNCmBgYA0KDQpUYXNrLTI6IGFpcnBvcnRzIGRhdGENCmBgYHtyfQ0KYWlycG9ydHMNCmBgYA0KVGFzay0zOiBwbGFuZXMgZGF0YQ0KYGBge3J9DQpwbGFuZXMgDQpgYGANClRhc2stNDogd2VhdGhlciBkYXRhDQpgYGB7cn0NCndlYXRoZXIgDQpgYGANCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMgS2V5cyANClRhc2stMUNvdW50aW5nIHRoZSBvY2N1cnJlbmNlcyBvZiBlYWNoIHRhaWwgbnVtYmVyIGluIHRoZSAicGxhbmVzIiB0YWJsZSBhbmQgZmlsdGVyaW5nIGZvciB0aG9zZSB3aXRoIG1vcmUgdGhhbiBvbmUgb2NjdXJyZW5jZS4NCmBgYHtyfQ0KcGxhbmVzICU+JSANCiAgY291bnQodGFpbG51bSkgJT4lIA0KICBmaWx0ZXIobiA+IDEpDQpgYGANCg0KVGFzay0yOkNvdW50aW5nIHRoZSBvY2N1cnJlbmNlcyBvZiBlYWNoIGNvbWJpbmF0aW9uIG9mIHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIGFuZCBvcmlnaW4gaW4gdGhlICJ3ZWF0aGVyIiB0YWJsZSBhbmQgZmlsdGVyaW5nIGZvciB0aG9zZSB3aXRoIG1vcmUgdGhhbiBvbmUgb2NjdXJyZW5jZS4NCmBgYHtyfQ0Kd2VhdGhlciAlPiUgDQogIGNvdW50KHllYXIsIG1vbnRoLCBkYXksIGhvdXIsIG9yaWdpbikgJT4lIA0KICBmaWx0ZXIobiA+IDEpDQpgYGANClRhc2stMzpDb3VudGluZyB0aGUgb2NjdXJyZW5jZXMgb2YgZWFjaCBjb21iaW5hdGlvbiBvZiB5ZWFyLCBtb250aCwgZGF5LCBhbmQgZmxpZ2h0IGluIHRoZSAiZmxpZ2h0cyIgdGFibGUgYW5kIGZpbHRlcmluZyBmb3IgdGhvc2Ugd2l0aCBtb3JlIHRoYW4gb25lIG9jY3VycmVuY2UuDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBjb3VudCh5ZWFyLCBtb250aCwgZGF5LCBmbGlnaHQpICU+JSANCiAgZmlsdGVyKG4gPiAxKQ0KYGBgDQpUYXNrLTQ6Q291bnRpbmcgdGhlIG9jY3VycmVuY2VzIG9mIGVhY2ggY29tYmluYXRpb24gb2YgeWVhciwgbW9udGgsIGRheSwgYW5kIHRhaWwgbnVtYmVyIGluIHRoZSAiZmxpZ2h0cyIgdGFibGUgYW5kIGZpbHRlcmluZyBmb3IgdGhvc2Ugd2l0aCBtb3JlIHRoYW4gb25lIG9jY3VycmVuY2UuDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBjb3VudCh5ZWFyLCBtb250aCwgZGF5LCB0YWlsbnVtKSAlPiUgDQogIGZpbHRlcihuID4gMSkNCmBgYA0KIyBNdXRhdGluZyBqb2lucw0KDQpUYXNrLTE6IENyZWF0aW5nIGEgc3Vic2V0IG9mIHRoZSAiZmxpZ2h0cyIgdGFibGUgbmFtZWQgImZsaWdodHMyIiBjb250YWluaW5nIGNvbHVtbnMgZnJvbSAieWVhciIgdG8gImRheSIsICJob3VyIiwgIm9yaWdpbiIsICJkZXN0IiwgInRhaWxudW0iLCBhbmQgImNhcnJpZXIiLg0KYGBge3J9DQpmbGlnaHRzMiA8LSBmbGlnaHRzICU+JSANCiAgc2VsZWN0KHllYXI6ZGF5LCBob3VyLCBvcmlnaW4sIGRlc3QsIHRhaWxudW0sIGNhcnJpZXIpDQpmbGlnaHRzMg0KYGBgDQpUYXNrLTI6UmVtb3ZpbmcgdGhlICJvcmlnaW4iIGFuZCAiZGVzdCIgY29sdW1ucyBmcm9tICJmbGlnaHRzMiIgdGFibGUgYW5kIHRoZW4gcGVyZm9ybWluZyBhIGxlZnQgam9pbiB3aXRoIHRoZSAiYWlybGluZXMiIHRhYmxlLCB1c2luZyB0aGUgImNhcnJpZXIiIGNvbHVtbiBhcyB0aGUga2V5IGZvciBtYXRjaGluZy4NCmBgYHtyfQ0KZmxpZ2h0czIgJT4lDQogIHNlbGVjdCgtb3JpZ2luLCAtZGVzdCkgJT4lIA0KICBsZWZ0X2pvaW4oYWlybGluZXMsIGJ5ID0gImNhcnJpZXIiKQ0KYGBgDQpUYXNrLTM6U2hvcnRlbmluZyB0aGUgY29tbWFuZCBieSByZW1vdmluZyAic2VsZWN0aW5nIiBhbmQgZGlyZWN0bHkgIm11dGF0aW5nIiB0aGUgIm5hbWUiIGNvbHVtbiB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIGFpcmxpbmUgbmFtZXMgZnJvbSB0aGUgImFpcmxpbmVzIiB0YWJsZSBiYXNlZCBvbiB0aGUgImNhcnJpZXIiIGNvbHVtbi4NCmBgYHtyfQ0KZmxpZ2h0czIgJT4lDQogIHNlbGVjdCgtb3JpZ2luLCAtZGVzdCkgJT4lIA0KICBtdXRhdGUobmFtZSA9IGFpcmxpbmVzJG5hbWVbbWF0Y2goY2FycmllciwgYWlybGluZXMkY2FycmllcildKQ0KYGBgDQojICBVbmRlcnN0YW5kaW5nIGpvaW5zDQpUYXNrLTE6Q3JlYXRpbmcgdHdvIHRpYmJsZXMsICJ4IiBhbmQgInkiLCBlYWNoIHdpdGggYSAia2V5IiBjb2x1bW4gYW5kIGFuIGFzc29jaWF0ZWQgInZhbF94IiBvciAidmFsX3kiIGNvbHVtbiwgcmVzcGVjdGl2ZWx5Lg0KYGBge3J9DQp4IDwtIHRyaWJibGUoDQogIH5rZXksIH52YWxfeCwNCiAgICAgMSwgIngxIiwNCiAgICAgMiwgIngyIiwNCiAgICAgMywgIngzIg0KKQ0KeSA8LSB0cmliYmxlKA0KICB+a2V5LCB+dmFsX3ksDQogICAgIDEsICJ5MSIsDQogICAgIDIsICJ5MiIsDQogICAgIDQsICJ5MyINCikNCg0KeA0KeQ0KYGBgDQoNCiMjIElubmVyIGpvaW4NClRhc2stMTpKb2luaW5nIHRpYmJsZXMgYHhgIGFuZCBgeWAgdXNpbmcgYW4gaW5uZXIgam9pbiBvcGVyYXRpb24gYmFzZWQgb24gdGhlICJrZXkiIGNvbHVtbi4NCmBgYHtyfQ0KeCAlPiUgDQogIGlubmVyX2pvaW4oeSwgYnkgPSAia2V5IikNCmBgYA0KIyMgRHVwbGljYXRlIGtleXMNClRhc2stMTogSm9pbmluZyB0aWJibGUgeCB3aXRoIHRpYmJsZSB5IHVzaW5nIHRoZSBjb21tb24gY29sdW1uICJrZXkiLg0KYGBge3J9DQp4IDwtIHRyaWJibGUoDQogIH5rZXksIH52YWxfeCwNCiAgICAgMSwgIngxIiwNCiAgICAgMiwgIngyIiwNCiAgICAgMiwgIngzIiwNCiAgICAgMSwgIng0Ig0KKQ0KeSA8LSB0cmliYmxlKA0KICB+a2V5LCB+dmFsX3ksDQogICAgIDEsICJ5MSIsDQogICAgIDIsICJ5MiINCikNCmBgYA0KDQpUYXNrLTI6UGVyZm9ybWluZyBhIGxlZnQgam9pbiBiZXR3ZWVuIHRpYmJsZSBgeGAgYW5kIHRpYmJsZSBgeWAgYmFzZWQgb24gdGhlIGNvbW1vbiBjb2x1bW4gImtleSIuDQpgYGB7cn0NCmxlZnRfam9pbih4LCB5LCBieSA9ICJrZXkiKQ0KYGBgDQoNClRhc2stMzpDcmVhdGluZyB0d28gdGliYmxlcywgYHhgIGFuZCBgeWAsIHdpdGggY29sdW1ucyAia2V5IiwgInZhbF94IiwgYW5kICJ2YWxfeSIsIHBvcHVsYXRlZCB3aXRoIGNvcnJlc3BvbmRpbmcgdmFsdWVzLg0KYGBge3J9DQp4IDwtIHRyaWJibGUoDQogIH5rZXksIH52YWxfeCwNCiAgICAgMSwgIngxIiwNCiAgICAgMiwgIngyIiwNCiAgICAgMiwgIngzIiwNCiAgICAgMywgIng0Ig0KKQ0KeSA8LSB0cmliYmxlKA0KICB+a2V5LCB+dmFsX3ksDQogICAgIDEsICJ5MSIsDQogICAgIDIsICJ5MiIsDQogICAgIDIsICJ5MyIsDQogICAgIDMsICJ5NCINCikNCmBgYA0KDQpUYXNrLTQ6UGVyZm9ybWluZyBhIGxlZnQgam9pbiBvbiB0aWJibGVzIGB4YCBhbmQgYHlgIHVzaW5nIHRoZSAia2V5IiBjb2x1bW4gYXMgdGhlIGpvaW4ga2V5Lg0KYGBge3J9DQpsZWZ0X2pvaW4oeCwgeSwgYnkgPSAia2V5IikNCmBgYA0KIyBEZWZpbmluZyB0aGUga2V5IGNvbHVtbnMNClRhc2stMTpQZXJmb3JtaW5nIGEgbGVmdCBqb2luIGJldHdlZW4gdGhlIGBmbGlnaHRzMmAgdGliYmxlIGFuZCB0aGUgYHdlYXRoZXJgIHRpYmJsZS4NCmBgYHtyfQ0KZmxpZ2h0czIgJT4lIA0KICBsZWZ0X2pvaW4od2VhdGhlcikNCmBgYA0KVGFzay0yOlBlcmZvcm1pbmcgYSBsZWZ0IGpvaW4gYmV0d2VlbiB0aGUgYGZsaWdodHMyYCB0aWJibGUgYW5kIHRoZSBgcGxhbmVzYCB0aWJibGUgdXNpbmcgdGhlICJ0YWlsbnVtIiBjb2x1bW4gYXMgdGhlIGtleS4NCmBgYHtyfQ0KZmxpZ2h0czIgJT4lIA0KICBsZWZ0X2pvaW4ocGxhbmVzLCBieSA9ICJ0YWlsbnVtIikNCmBgYA0KVGFzay0zOlBlcmZvcm1pbmcgYSBsZWZ0IGpvaW4gYmV0d2VlbiB0aGUgYGZsaWdodHMyYCB0aWJibGUgYW5kIHRoZSBgYWlycG9ydHNgIHRpYmJsZSwgbWF0Y2hpbmcgdGhlICJkZXN0IiBjb2x1bW4gZnJvbSBgZmxpZ2h0czJgIHdpdGggdGhlICJmYWEiIGNvbHVtbiBmcm9tIGBhaXJwb3J0c2AuDQpgYGB7cn0NCmZsaWdodHMyICU+JSANCiAgbGVmdF9qb2luKGFpcnBvcnRzLCBjKCJkZXN0IiA9ICJmYWEiKSkNCmBgYA0KDQpUYXNrLTQ6UGVyZm9ybWluZyBhIGxlZnQgam9pbiBiZXR3ZWVuIHRoZSBgZmxpZ2h0czJgIHRpYmJsZSBhbmQgdGhlIGBhaXJwb3J0c2AgdGliYmxlLCBtYXRjaGluZyB0aGUgIm9yaWdpbiIgY29sdW1uIGZyb20gYGZsaWdodHMyYCB3aXRoIHRoZSAiZmFhIiBjb2x1bW4gZnJvbSBgYWlycG9ydHNgLg0KYGBge3J9DQpmbGlnaHRzMiAlPiUgDQogIGxlZnRfam9pbihhaXJwb3J0cywgYygib3JpZ2luIiA9ICJmYWEiKSkNCmBgYA0KIyAgRmlsdGVyaW5nIGpvaW5zDQpUYXNrLTE6IENhbGN1bGF0aW5nIHRoZSB0b3AgMTAgZGVzdGluYXRpb25zIGJ5IGNvdW50aW5nIHRoZSBvY2N1cnJlbmNlcyBpbiB0aGUgImRlc3QiIGNvbHVtbiBvZiB0aGUgYGZsaWdodHNgIHRpYmJsZSwgc29ydGVkIGluIGRlc2NlbmRpbmcgb3JkZXIsIGFuZCB0aGVuIGRpc3BsYXlpbmcgdGhlIHJlc3VsdC4NCmBgYHtyfQ0KdG9wX2Rlc3QgPC0gZmxpZ2h0cyAlPiUNCiAgY291bnQoZGVzdCwgc29ydCA9IFRSVUUpICU+JQ0KICBoZWFkKDEwKQ0KdG9wX2Rlc3QNCmBgYA0KVGFzay0yOiBGaWx0ZXJpbmcgdGhlIGBmbGlnaHRzYCB0aWJibGUgdG8gaW5jbHVkZSBvbmx5IHJvd3Mgd2hlcmUgdGhlIGRlc3RpbmF0aW9uIChgZGVzdGApIG1hdGNoZXMgYW55IG9mIHRoZSB0b3AgMTAgZGVzdGluYXRpb25zIGlkZW50aWZpZWQgaW4gdGhlIHByZXZpb3VzIHN0ZXAuDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBmaWx0ZXIoZGVzdCAlaW4lIHRvcF9kZXN0JGRlc3QpDQojJWluJSBvcGVyYXRvciBpbiBSIGlzIHVzZWQgdG8gY2hlY2sgaWYgZWxlbWVudHMgaW4gb25lIHZlY3RvciBhcmUgcHJlc2VudCBpbiBhbm90aGVyIHZlY3Rvcg0KYGBgDQoNClRhc2stMzogU2VsZWN0aW5nIHJvd3MgZnJvbSB0aGUgYGZsaWdodHNgIGRhdGFzZXQgd2hlcmUgdGhlIGRlc3RpbmF0aW9uIGFpcnBvcnQgbWF0Y2hlcyBvbmUgb2YgdGhlIHRvcCAxMCBkZXN0aW5hdGlvbnMgcHJldmlvdXNseSBpZGVudGlmaWVkLg0KYGBge3J9DQpmbGlnaHRzICU+JSANCiAgc2VtaV9qb2luKHRvcF9kZXN0KQ0KYGBgDQpUYXNrLTQ6IEZpbHRlcmluZyBvdXQgZmxpZ2h0cyB3aXRoIHRhaWwgbnVtYmVycyBwcmVzZW50IGluIHRoZSBwbGFuZXMgZGF0YXNldCBhbmQgY291bnRpbmcgdGhlIG9jY3VycmVuY2VzIG9mIGVhY2ggdW5pcXVlIHRhaWwgbnVtYmVyLCBzb3J0aW5nIHRoZSByZXN1bHQuDQpgYGB7cn0NCmZsaWdodHMgJT4lDQogIGFudGlfam9pbihwbGFuZXMsIGJ5ID0gInRhaWxudW0iKSAlPiUNCiAgY291bnQodGFpbG51bSwgc29ydCA9IFRSVUUpDQpgYGANCiMgU2V0IG9wZXJhdGlvbnMNClRhc2stMTpjcmVhdGluZyB0d28gdGliYmxlcywgZGYxIGFuZCBkZjIsIGVhY2ggd2l0aCBjb2x1bW5zIHggYW5kIHksIGNvbnRhaW5pbmcgc2FtcGxlIGRhdGEuDQpgYGB7cn0NCmRmMSA8LSB0cmliYmxlKA0KICB+eCwgfnksDQogICAxLCAgMSwNCiAgIDIsICAxDQopDQpkZjIgPC0gdHJpYmJsZSgNCiAgfngsIH55LA0KICAgMSwgIDEsDQogICAxLCAgMg0KKQ0KYGBgDQoNClRhc2stMjpwZXJmb3JtaW5nIHNldCBvcGVyYXRpb25zIG9uIHRoZSB0aWJibGVzIGRmMSBhbmQgZGYyLCBpbmNsdWRpbmcgaW50ZXJzZWN0aW9uLCB1bmlvbiwgYW5kIHNldCBkaWZmZXJlbmNlcy4NCmBgYHtyfQ0KaW50ZXJzZWN0KGRmMSwgZGYyKQ0KdW5pb24oZGYxLCBkZjIpDQpzZXRkaWZmKGRmMSwgZGYyKQ0Kc2V0ZGlmZihkZjIsIGRmMSkNCmBgYA0KDQojIENILTE0OiBTdHJpbmdzDQpCYXNpYyBJbmZvOnN0cmluZzEgPC0gIlRoaXMgaXMgYSBzdHJpbmciDQogICAgICAgICAgIHN0cmluZzIgPC0gJ0lmIEkgd2FudCB0byBpbmNsdWRlIGEgInF1b3RlIiBpbnNpZGUgYSBzdHJpbmcsIEkgdXNlIHNpbmdsZSBxdW90ZXMnDQogICAgICAgICAgIA0KVGFzay0xOlRvIGluY2x1ZGUgYSBsaXRlcmFsIHNpbmdsZSBvciBkb3VibGUgcXVvdGUgaW4gYSBzdHJpbmcgeW91IGNhbiB1c2UgXCB0byDigJxlc2NhcGXigJ0gaXQgICAgICAgICANCmBgYHtyfQ0KZG91YmxlX3F1b3RlIDwtICJcIiIgIyBvciAnIicNCnNpbmdsZV9xdW90ZSA8LSAnXCcnICMgb3IgIiciDQpgYGANCg0KVGFzay0yOiBVbmRlcnN0YW5kaW5nIHRoZSBjaGFyYWN0ZXIgDQpgYGB7cn0NCg0KeCA8LSBjKCJcIiIsICJcXCIpICNiYWNrc2xhc2ggaXMgZXNjYXBlIGNoYXJhY3Rlcg0KeA0Kd3JpdGVMaW5lcyh4KQ0KYGBgDQojICBTdHJpbmcgbGVuZ3RoDQpUYXNrLTE6DQpgYGB7cn0NCnN0cl9sZW5ndGgoYygiYSIsICJSIGZvciBkYXRhIHNjaWVuY2UiLCBOQSkpDQpgYGANCg0KIyBDb21iaW5pbmcgc3RyaW5ncw0KVGFzay0xOkNvbWJpbmluZyAgdGhlIHN0cmluZ3MNCmBgYHtyfQ0Kc3RyX2MoIngiLCAieSIpDQpzdHJfYygieCIsICJ5IiwgInoiKQ0KYGBgDQpUYXNrLTI6VXNpbmcgdGhlIHNlcCBhcmd1bWVudCB0byBjb250cm9sIGhvdyB0aGV54oCZcmUgc2VwYXJhdGVkLg0KYGBge3J9DQpzdHJfYygieCIsICJ5Iiwgc2VwID0gIiwgIikNCmBgYA0KVGFzay0zOlBlcmZvcm1pbmcgY29uY2F0ZW5hdGlvbiB3aXRoICJ8IiBhbmQgIi0iIGF0IGJvdGggZW5kcyBvZiBlYWNoIGVsZW1lbnQgb2YgdmVjdG9yIHgsIGFuZCByZXBsYWNpbmcgTkEgdmFsdWVzIHdpdGggZW1wdHkgc3RyaW5ncyBiZWZvcmUgY29uY2F0ZW5hdGlvbi4NCmBgYHtyfQ0KeCA8LSBjKCJhYmMiLCBOQSkNCnN0cl9jKCJ8LSIsIHgsICItfCIpDQpzdHJfYygifC0iLCBzdHJfcmVwbGFjZV9uYSh4KSwgIi18IikNCmBgYA0KVGFzay00OiBjb25jYXRlbmF0aW5nIGVhY2ggZWxlbWVudCBvZiB0aGUgdmVjdG9yIGMoImEiLCAiYiIsICJjIikgd2l0aCBhIHByZWZpeCAicHJlZml4LSIgYW5kIGEgc3VmZml4ICItc3VmZml4Ii4NCmBgYHtyfQ0Kc3RyX2MoInByZWZpeC0iLCBjKCJhIiwgImIiLCAiYyIpLCAiLXN1ZmZpeCIpDQpgYGANClRhc2stNTogY29tYmluaW5nIHN0cmluZ3MNCmBgYHtyfQ0KbmFtZSA8LSAiSGFkbGV5Ig0KdGltZV9vZl9kYXkgPC0gIm1vcm5pbmciDQpiaXJ0aGRheSA8LSBGQUxTRQ0KDQpzdHJfYygNCiAgIkdvb2QgIiwgdGltZV9vZl9kYXksICIgIiwgbmFtZSwNCiAgaWYgKGJpcnRoZGF5KSAiIGFuZCBIQVBQWSBCSVJUSERBWSIsDQogICIuIg0KKQ0KYGBgDQojIFN1YnNldHRpbmcgc3RyaW5ncw0KVGFzay0xOkV4dHJhY3RpbmcgdGhlIGZpcnN0IHRocmVlIGNoYXJhY3RlcnMgZnJvbSBlYWNoIGVsZW1lbnQgaW4gdGhlIHZlY3RvciBgeGAgdXNpbmcgYHN0cl9zdWJgLg0KYGBge3J9DQp4IDwtIGMoIkFwcGxlIiwgIkJhbmFuYSIsICJQZWFyIikNCnN0cl9zdWIoeCwgMSwgMykNCmBgYA0KVGFzay0yOm5lZ2F0aXZlIG51bWJlcnMgY291bnQgYmFja3dhcmRzIGZyb20gZW5kDQpgYGB7cn0NCnN0cl9zdWIoeCwgLTMsIC0xKQ0KYGBgDQpUYXNrLTM6dXNpbmcgdGhlIGFzc2lnbm1lbnQgZm9ybSBvZiBzdHJfc3ViKCkgdG8gbW9kaWZ5IHN0cmluZ3MNCmBgYHtyfQ0Kc3RyX3N1Yih4LCAxLCAxKSA8LSBzdHJfdG9fbG93ZXIoc3RyX3N1Yih4LCAxLCAxKSkNCngNCmBgYA0KDQojIExvY2FsZXMNClRhc2stMTpDaGFuZ2luZyB0aGUgY2FzZSANCmBgYHtyfQ0Kc3RyX3RvX3VwcGVyKGMoImkiLCAixLEiKSkNCnN0cl90b191cHBlcihjKCJpIiwgIsSxIiksIGxvY2FsZSA9ICJ0ciIpDQpgYGANClRhc2stMjpTb3J0aW5nIHRoZSBjaGFyYWN0ZXIgdmVjdG9yIHggYWxwaGFiZXRpY2FsbHkgdXNpbmcgdGhlIEVuZ2xpc2ggKGVuKSBsb2NhbGUgYW5kIHRoZSBIYXdhaWlhbiAoaGF3KSBsb2NhbGUuDQpgYGB7cn0NCnggPC0gYygiYXBwbGUiLCAiZWdncGxhbnQiLCAiYmFuYW5hIikNCnN0cl9zb3J0KHgsIGxvY2FsZSA9ICJlbiIpIA0Kc3RyX3NvcnQoeCwgbG9jYWxlID0gImhhdyIpIA0KYGBgDQojICBNYXRjaGluZyBwYXR0ZXJucyB3aXRoIHJlZ3VsYXIgZXhwcmVzc2lvbnMNCg0KIyMgQmFzaWMgbWF0Y2hlcw0KVGFzay0xOlNlYXJjaGluZyBmb3IgdGhlIHBhdHRlcm4gImFuIiB3aXRoaW4gZWFjaCBlbGVtZW50IG9mIGB4YCBhbmQgZGlzcGxheWluZyB0aGUgbWF0Y2hlcy4NCmBgYHtyfQ0KeCA8LSBjKCJhcHBsZSIsICJiYW5hbmEiLCAicGVhciIpDQpzdHJfdmlldyh4LCAiYW4iKQ0KYGBgDQoNClRhc2stMjpEaXNwbGF5aW5nIGVsZW1lbnRzIGluIGB4YCB3aGVyZSBhbnkgY2hhcmFjdGVyIGlzIGZvbGxvd2VkIGJ5ICJhIiBhbmQgdGhlbiBhbnkgY2hhcmFjdGVyLg0KYGBge3J9DQpzdHJfdmlldyh4LCAiLmEuIikNCmBgYA0KVGFzay0zIA0KYGBge3J9DQojIFRvIGNyZWF0ZSB0aGUgcmVndWxhciBleHByZXNzaW9uLCB3ZSBuZWVkIFxcDQpkb3QgPC0gIlxcLiINCg0KIyBCdXQgdGhlIGV4cHJlc3Npb24gaXRzZWxmIG9ubHkgY29udGFpbnMgb25lOg0Kd3JpdGVMaW5lcyhkb3QpDQoNCiMgQW5kIHRoaXMgdGVsbHMgUiB0byBsb29rIGZvciBhbiBleHBsaWNpdCAuDQpzdHJfdmlldyhjKCJhYmMiLCAiYS5jIiwgImJlZiIpLCAiYVxcLmMiKQ0KDQpgYGANClRhc2stNDogRGlzcGxheWluZyBlbGVtZW50cyBpbiBgeGAgd2hlcmUgdGhlIHNlcXVlbmNlICJcXCIgb2NjdXJzLg0KYGBge3J9DQp4IDwtICJhXFxiIg0Kd3JpdGVMaW5lcyh4KQ0KDQpzdHJfdmlldyh4LCAiXFxcXCIpDQpgYGANCiMjICBBbmNob3JzDQpUYXNrLTE6IERpc3BsYXlpbmcgZWxlbWVudHMgaW4gYHhgIHRoYXQgc3RhcnQgd2l0aCAiYSIgYW5kIGVuZCB3aXRoICJhIiByZXNwZWN0aXZlbHkuDQpgYGB7cn0NCnggPC0gYygiYXBwbGUiLCAiYmFuYW5hIiwgInBlYXIiKQ0Kc3RyX3ZpZXcoeCwgIl5hIikNCnN0cl92aWV3KHgsICJhJCIpDQpgYGANClRhc2stMjogSGlnaGxpZ2h0aW5nICJhcHBsZSIgb2NjdXJyZW5jZXMgaW4gYHhgIGFuZCBpbnN0YW5jZXMgd2hlcmUgaXQncyB0aGUgb25seSBjb250ZW50Lg0KYGBge3J9DQp4IDwtIGMoImFwcGxlIHBpZSIsICJhcHBsZSIsICJhcHBsZSBjYWtlIikNCnN0cl92aWV3KHgsICJhcHBsZSIpDQpzdHJfdmlldyh4LCAiXmFwcGxlJCIpDQpgYGANCiMjIENoYXJhY3RlciBjbGFzc2VzIGFuZCBhbHRlcm5hdGl2ZXMNCg0KVGFzay0xOiBWaXN1YWxpemluZyBwYXR0ZXJucyBtYXRjaGluZyAiYS5jIiwgImEqYyIsIGFuZCAiYSBjIiBpbiB0aGUgcHJvdmlkZWQgY2hhcmFjdGVyIHZlY3Rvci4NCmBgYHtyfQ0Kc3RyX3ZpZXcoYygiYWJjIiwgImEuYyIsICJhKmMiLCAiYSBjIiksICJhWy5dYyIpDQpzdHJfdmlldyhjKCJhYmMiLCAiYS5jIiwgImEqYyIsICJhIGMiKSwgIi5bKl1jIikNCnN0cl92aWV3KGMoImFiYyIsICJhLmMiLCAiYSpjIiwgImEgYyIpLCAiYVsgXSIpDQpgYGANClRhc2stMjogVmlzdWFsaXppbmcgcGF0dGVybnMgbWF0Y2hpbmcgImdyZXkiIG9yICJncmF5IiBpbiB0aGUgcHJvdmlkZWQgY2hhcmFjdGVyIHZlY3Rvci4NCmBgYHtyfQ0Kc3RyX3ZpZXcoYygiZ3JleSIsICJncmF5IiksICJncihlfGEpeSIpDQpgYGANCiMjIFJlcGV0aXRpb24NClRhc2stMTpJZGVudGlmeWluZyBwYXR0ZXJucyAiQ0MiIG9yICJDIiBpbiB0aGUgc3RyaW5nICIxODg4IGlzIHRoZSBsb25nZXN0IHllYXIgaW4gUm9tYW4gbnVtZXJhbHMNCmBgYHtyfQ0KeCA8LSAiMTg4OCBpcyB0aGUgbG9uZ2VzdCB5ZWFyIGluIFJvbWFuIG51bWVyYWxzOiBNRENDQ0xYWFhWSUlJIg0Kc3RyX3ZpZXcoeCwgIkNDPyIpDQpgYGANClRhc2stMjogVmlld2luZyB0aGUgcGF0dGVybiAiQ0MiDQpgYGB7cn0NCnN0cl92aWV3KHgsICJDQysiKQ0KYGBgDQpUYXNrLTM6IFZpZXdpbmcgdGhlIHBhdHRlcm4gIkNbTFhdKyINCmBgYHtyfQ0Kc3RyX3ZpZXcoeCwgJ0NbTFhdKycpDQpgYGANClRhc2stNDpWaWV3aW5nIHRoZSBwYXR0ZXJuICJDezJ9LEN7Mix9LGN7MiwzfSINCmBgYHtyfQ0Kc3RyX3ZpZXcoeCwgIkN7Mn0iKQ0Kc3RyX3ZpZXcoeCwgIkN7Mix9IikNCnN0cl92aWV3KHgsICJDezIsM30iKQ0KYGBgDQojIyBHcm91cGluZyBhbmQgYmFja3JlZmVyZW5jZXMNClRhc2stMTpHcm91cGluZw0KYGBge3J9DQpzdHJfdmlldyhmcnVpdCwgIiguLilcXDEiLCBtYXRjaCA9IFRSVUUpDQpgYGANCg0KIyMgRGV0ZWN0IG1hdGNoZXMNClRhc2stMTogQ2hlY2tpbmcgZm9yIHRoZSBwcmVzZW5jZSBvZiB0aGUgbGV0dGVyICJlIiBpbiBlYWNoIHdvcmQgDQpgYGB7cn0NCnggPC0gYygiYXBwbGUiLCAiYmFuYW5hIiwgInBlYXIiKQ0Kc3RyX2RldGVjdCh4LCAiZSIpDQpgYGANCg0KVGFzay0yOkNoZWNraW5nIGhvdyBtYW55IGNvbW1vbiB3b3JkcyBzdGFydCB3aXRoIHQNCmBgYHtyfQ0Kc3VtKHN0cl9kZXRlY3Qod29yZHMsICJedCIpKQ0KYGBgDQoNClRhc2stMzogQ2hlY2tpbmcgcHJvcG9ydGlvbiBvZiBjb21tb24gd29yZHMgZW5kIHdpdGggYSB2b3dlbA0KYGBge3J9DQptZWFuKHN0cl9kZXRlY3Qod29yZHMsICJbYWVpb3VdJCIpKQ0KYGBgDQpUYXNrLTQ6RmluZGluZyBhbGwgd29yZHMgY29udGFpbmluZyBhdCBsZWFzdCBvbmUgdm93ZWwsIGFuZCBuZWdhdGUNCmBgYHtyfQ0Kbm9fdm93ZWxzXzEgPC0gIXN0cl9kZXRlY3Qod29yZHMsICJbYWVpb3VdIikNCmBgYA0KDQpUYXNrLTU6RmluZGluZyBhbGwgd29yZHMgY29uc2lzdGluZyBvbmx5IG9mIGNvbnNvbmFudHMgKG5vbi12b3dlbHMpDQpgYGB7cn0NCm5vX3Zvd2Vsc18yIDwtIHN0cl9kZXRlY3Qod29yZHMsICJeW15hZWlvdV0rJCIpDQppZGVudGljYWwobm9fdm93ZWxzXzEsIG5vX3Zvd2Vsc18yKQ0KYGBgDQoNClRhc2stNjogRmlsdGVyaW5nIHdvcmRzIHRoYXQgZW5kIHdpdGggdGhlIGxldHRlciAieCIgZnJvbSBhIGxpc3Qgb2Ygd29yZHMuDQpgYGB7cn0NCndvcmRzW3N0cl9kZXRlY3Qod29yZHMsICJ4JCIpXQ0Kc3RyX3N1YnNldCh3b3JkcywgIngkIikNCmBgYA0KVGFzay03OiBGaWx0ZXJpbmcgYSB0aWJibGUgZm9yIHdvcmRzIHRoYXQgZW5kIHdpdGggIngiLg0KYGBge3J9DQpkZiA8LSB0aWJibGUoDQogIHdvcmQgPSB3b3JkcywgDQogIGkgPSBzZXFfYWxvbmcod29yZCkNCikNCmRmICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3Qod29yZCwgIngkIikpDQpgYGANCg0KVGFzay04OkNvdW50aW5nIHRoZSBvY2N1cnJlbmNlcyBvZiAiYSIgaW4gZWFjaCBlbGVtZW50IG9mIGEgY2hhcmFjdGVyIHZlY3Rvci4NCmBgYHtyfQ0KeCA8LSBjKCJhcHBsZSIsICJiYW5hbmEiLCAicGVhciIpDQpzdHJfY291bnQoeCwgImEiKQ0KYGBgDQpUYXNrLTk6IFNlZWluZyBhdmVyYWdlIG9mIGhvdyBtYW55IHZvd2VscyBwZXIgd29yZA0KYGBge3J9DQptZWFuKHN0cl9jb3VudCh3b3JkcywgIlthZWlvdV0iKSkNCmBgYA0KVGFzay0xMDogQWRkaW5nIGNvbHVtbnMgdG8gYSB0aWJibGUgdG8gY291bnQgdm93ZWxzIGFuZCBjb25zb25hbnRzIGluIGVhY2ggd29yZC4NCmBgYHtyfQ0KZGYgJT4lIA0KICBtdXRhdGUoDQogICAgdm93ZWxzID0gc3RyX2NvdW50KHdvcmQsICJbYWVpb3VdIiksDQogICAgY29uc29uYW50cyA9IHN0cl9jb3VudCh3b3JkLCAiW15hZWlvdV0iKQ0KICApDQpgYGANCg0KVGFzay0xMTpDb3VudGluZyAiYWJhIiBvY2N1cnJlbmNlcyBpbiAiYWJhYmFiYSIgYW5kIHNob3dpbmcgYWxsICJhYmEiIGluc3RhbmNlcy4NCmBgYHtyfQ0Kc3RyX2NvdW50KCJhYmFiYWJhIiwgImFiYSIpDQpzdHJfdmlld19hbGwoImFiYWJhYmEiLCAiYWJhIikNCmBgYA0KIyMgRXh0cmFjdCBtYXRjaGVzDQpUYXNrLTE6IERpc3BsYXlpbmcgdGhlIGxlbmd0aCBvZiBzZW50ZW5jZXMgYW5kIHNob3dpbmcgdGhlIGZpcnN0IGZldyBzZW50ZW5jZXMuDQpgYGB7cn0NCmxlbmd0aChzZW50ZW5jZXMpDQpoZWFkKHNlbnRlbmNlcykNCmBgYA0KDQpUYXNrLTI6IENyZWF0aW5nIGEgc3RyaW5nIHBhdHRlcm4gdG8gbWF0Y2ggY29sb3JzIGJ5IGNvbmNhdGVuYXRpbmcgdGhlbSB3aXRoIGEgcGlwZSBkZWxpbWl0ZXIuDQpgYGB7cn0NCmNvbG91cnMgPC0gYygicmVkIiwgIm9yYW5nZSIsICJ5ZWxsb3ciLCAiZ3JlZW4iLCAiYmx1ZSIsICJwdXJwbGUiKQ0KY29sb3VyX21hdGNoIDwtIHN0cl9jKGNvbG91cnMsIGNvbGxhcHNlID0gInwiKQ0KY29sb3VyX21hdGNoDQpgYGANClRhc2stMzogRmlsdGVyIHNlbnRlbmNlcyBmb3IgY29sb3JzIGFuZCBleHRyYWN0IG1hdGNoZXMsIHNob3dpbmcgdGhlIGZpcnN0IGZldy4NCmBgYHtyfQ0KaGFzX2NvbG91ciA8LSBzdHJfc3Vic2V0KHNlbnRlbmNlcywgY29sb3VyX21hdGNoKQ0KbWF0Y2hlcyA8LSBzdHJfZXh0cmFjdChoYXNfY29sb3VyLCBjb2xvdXJfbWF0Y2gpDQpoZWFkKG1hdGNoZXMpDQpgYGANClRhc2stNDpTaG93aW5nIGFsbCBzZW50ZW5jZXMgY29udGFpbmluZyBtdWx0aXBsZSBjb2xvcnMgYW5kIGhpZ2hsaWdodCB0aGUgbWF0Y2hlcy4NCmBgYHtyfQ0KbW9yZSA8LSBzZW50ZW5jZXNbc3RyX2NvdW50KHNlbnRlbmNlcywgY29sb3VyX21hdGNoKSA+IDFdDQpzdHJfdmlld19hbGwobW9yZSwgY29sb3VyX21hdGNoKQ0KYGBgDQpUYXNrLTU6RXh0cmFjdGluZyBhbGwgY29sb3IgbWF0Y2hlcyBmcm9tIHRoZSBzdWJzZXQgb2Ygc2VudGVuY2VzIGNvbnRhaW5pbmcgbXVsdGlwbGUgY29sb3JzLg0KYGBge3J9DQpzdHJfZXh0cmFjdChtb3JlLCBjb2xvdXJfbWF0Y2gpDQpgYGANCg0KVGFzay02OkV4dHJhY3RpbmcgYWxsIG9jY3VycmVuY2VzIG9mIGNvbG9ycyBmcm9tIHRoZSBzdWJzZXQgb2Ygc2VudGVuY2VzIGNvbnRhaW5pbmcgbXVsdGlwbGUgY29sb3JzLg0KYGBge3J9DQpzdHJfZXh0cmFjdF9hbGwobW9yZSwgY29sb3VyX21hdGNoKQ0KYGBgDQpUYXNrLTc6IEV4dHJhY3RpbmcgY29sb3JzIGZyb20gc2VudGVuY2VzIHdpdGggbXVsdGlwbGUgY29sb3JzIGFuZCBzaW1wbGlmeSwgYWxzbyBleHRyYWN0IGxvd2VyY2FzZSBsZXR0ZXJzIGZyb20gZWFjaCBlbGVtZW50IGluIHggYW5kIHNpbXBsaWZ5Lg0KYGBge3J9DQpzdHJfZXh0cmFjdF9hbGwobW9yZSwgY29sb3VyX21hdGNoLCBzaW1wbGlmeSA9IFRSVUUpDQp4IDwtIGMoImEiLCAiYSBiIiwgImEgYiBjIikNCnN0cl9leHRyYWN0X2FsbCh4LCAiW2Etel0iLCBzaW1wbGlmeSA9IFRSVUUpDQpgYGANCiMjIEdyb3VwZWQgbWF0Y2hlcyANClRhc2stMTogRXh0cmFjdGluZyBzZW50ZW5jZXMgY29udGFpbmluZyBub3VucyBkZWZpbmVkIGJ5IGEgcGF0dGVybiwgdGhlbiBleHRyYWN0cyB0aGUgbm91bnMgZnJvbSB0aG9zZSBzZW50ZW5jZXMuDQpgYGB7cn0NCm5vdW4gPC0gIihhfHRoZSkgKFteIF0rKSINCg0KaGFzX25vdW4gPC0gc2VudGVuY2VzICU+JQ0KICBzdHJfc3Vic2V0KG5vdW4pICU+JQ0KICBoZWFkKDEwKQ0KaGFzX25vdW4gJT4lIA0KICBzdHJfZXh0cmFjdChub3VuKQ0KYGBgDQoNClRhc2stMjoNCmBgYHtyfQ0KaGFzX25vdW4gJT4lIA0KICBzdHJfbWF0Y2gobm91bikNCmBgYA0KVGFzay0zOkNyZWF0aW5nIGEgdGliYmxlIHdpdGggY29sdW1ucyAnYXJ0aWNsZScgYW5kICdub3VuJyBleHRyYWN0ZWQgZnJvbSBzZW50ZW5jZXMgYmFzZWQgb24gYSBwYXR0ZXJuLg0KYGBge3J9DQp0aWJibGUoc2VudGVuY2UgPSBzZW50ZW5jZXMpICU+JSANCiAgdGlkeXI6OmV4dHJhY3QoDQogICAgc2VudGVuY2UsIGMoImFydGljbGUiLCAibm91biIpLCAiKGF8dGhlKSAoW14gXSspIiwgDQogICAgcmVtb3ZlID0gRkFMU0UNCiAgKQ0KYGBgDQojIyBSZXBsYWNpbmcgbWF0Y2hlcw0KVGFzay0xOiBSZXBsYWNpbmcgdGhlIGZpcnN0IHZvd2VsIGluIGVhY2ggd29yZCBvZiB4IHdpdGggYSBoeXBoZW4uDQogICAgICAgIFJlcGxhY2luZyBhbGwgdm93ZWxzIGluIGVhY2ggd29yZCBvZiB4IHdpdGggYSBoeXBoZW4uDQpgYGB7cn0NCnggPC0gYygiYXBwbGUiLCAicGVhciIsICJiYW5hbmEiKQ0Kc3RyX3JlcGxhY2UoeCwgIlthZWlvdV0iLCAiLSIpDQpzdHJfcmVwbGFjZV9hbGwoeCwgIlthZWlvdV0iLCAiLSIpDQpgYGANClRhc2stMjogUmVwbGFjaW5nIG51bWVyaWMgdmFsdWVzIGluIHggd2l0aCB0aGVpciBjb3JyZXNwb25kaW5nIHdvcmQgcmVwcmVzZW50YXRpb25zLg0KYGBge3J9DQp4IDwtIGMoIjEgaG91c2UiLCAiMiBjYXJzIiwgIjMgcGVvcGxlIikNCnN0cl9yZXBsYWNlX2FsbCh4LCBjKCIxIiA9ICJvbmUiLCAiMiIgPSAidHdvIiwgIjMiID0gInRocmVlIikpDQpgYGANClRhc2stMzpSZW9yZGVyaW5nIHdvcmRzIGluIHNlbnRlbmNlcyBieSBzd2FwcGluZyB0aGUgc2Vjb25kIGFuZCB0aGlyZCB3b3JkIHBvc2l0aW9ucy4NCmBgYHtyfQ0Kc2VudGVuY2VzICU+JSANCiAgc3RyX3JlcGxhY2UoIihbXiBdKykgKFteIF0rKSAoW14gXSspIiwgIlxcMSBcXDMgXFwyIikgJT4lIA0KICBoZWFkKDUpDQpgYGANCiMgU3BsaXR0aW5nDQpUYXNrLTE6IFNwbGl0dGluZyB0aGUgZmlyc3QgZml2ZSBzZW50ZW5jZXMgaW50byB3b3Jkcy4NCmBgYHtyfQ0Kc2VudGVuY2VzICU+JQ0KICBoZWFkKDUpICU+JSANCiAgc3RyX3NwbGl0KCIgIikNCmBgYA0KVGFzay0yOlNwbGl0dGluZyB0aGUgc3RyaW5nICdhfGJ8Y3xkJyBieSAnfCcgaW50byBhIHZlY3RvciBvZiBlbGVtZW50cy4NCmBgYHtyfQ0KImF8YnxjfGQiICU+JSANCiAgc3RyX3NwbGl0KCJcXHwiKSAlPiUgDQogIC5bWzFdXQ0KYGBgDQpUYXNrLTM6U3BsaXR0aW5nIHRoZSBmaXJzdCA1IHNlbnRlbmNlcyBieSBzcGFjZSBpbnRvIGEgbWF0cml4IG9mIHdvcmRzLg0KYGBge3J9DQpzZW50ZW5jZXMgJT4lDQogIGhlYWQoNSkgJT4lIA0KICBzdHJfc3BsaXQoIiAiLCBzaW1wbGlmeSA9IFRSVUUpDQpgYGANClRhc2stNDpTcGxpdHRpbmcgZWFjaCBmaWVsZCBzdHJpbmcgaW50byB0d28gcGFydHMgYXQgdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgJzogJy4NCmBgYHtyfQ0KZmllbGRzIDwtIGMoIk5hbWU6IEhhZGxleSIsICJDb3VudHJ5OiBOWiIsICJBZ2U6IDM1IikNCmZpZWxkcyAlPiUgc3RyX3NwbGl0KCI6ICIsIG4gPSAyLCBzaW1wbGlmeSA9IFRSVUUpDQpgYGANClRhc2stNTogRGlzcGxheSB3b3JkIGJvdW5kYXJpZXMsIHNwbGl0IGJ5IHNwYWNlcywgYW5kIHNwbGl0IGJ5IHdvcmQgYm91bmRhcmllcywgcmVzcGVjdGl2ZWx5Lg0KYGBge3J9DQp4IDwtICJUaGlzIGlzIGEgc2VudGVuY2UuICBUaGlzIGlzIGFub3RoZXIgc2VudGVuY2UuIg0Kc3RyX3ZpZXdfYWxsKHgsIGJvdW5kYXJ5KCJ3b3JkIikpDQpzdHJfc3BsaXQoeCwgIiAiKVtbMV1dDQpzdHJfc3BsaXQoeCwgYm91bmRhcnkoIndvcmQiKSlbWzFdXQ0KYGBgDQojIE90aGVyIHR5cGVzIG9mIHBhdHRlcm4NCg0KVGFzay0xOiANCmBgYHtyfQ0KIyBUaGUgcmVndWxhciBjYWxsOg0Kc3RyX3ZpZXcoZnJ1aXQsICJuYW5hIikNCiMgSXMgc2hvcnRoYW5kIGZvcg0Kc3RyX3ZpZXcoZnJ1aXQsIHJlZ2V4KCJuYW5hIikpDQpgYGANClRhc2stMjpWaXN1YWxpemluZyBvY2N1cnJlbmNlcyBvZiAiYmFuYW5hIiBpbiBkaWZmZXJlbnQgY2FzZSB2YXJpYXRpb25zLg0KYGBge3J9DQpiYW5hbmFzIDwtIGMoImJhbmFuYSIsICJCYW5hbmEiLCAiQkFOQU5BIikNCnN0cl92aWV3KGJhbmFuYXMsICJiYW5hbmEiKQ0Kc3RyX3ZpZXcoYmFuYW5hcywgcmVnZXgoImJhbmFuYSIsIGlnbm9yZV9jYXNlID0gVFJVRSkpDQpgYGANClRhc2stMzogRXh0cmFjdGluZyBhbGwgbGluZXMgc3RhcnRpbmcgd2l0aCAiTGluZSIgZnJvbSB0aGUgdGV4dC4NCmBgYHtyfQ0KeCA8LSAiTGluZSAxXG5MaW5lIDJcbkxpbmUgMyINCnN0cl9leHRyYWN0X2FsbCh4LCAiXkxpbmUiKVtbMV1dDQpgYGANClRhc2stNDogRXh0cmFjdGluZyBhbGwgb2NjdXJyZW5jZXMgb2YgbGluZXMgc3RhcnRpbmcgd2l0aCAiTGluZSIgZnJvbSB0aGUgdGV4dCwgY29uc2lkZXJpbmcgZWFjaCBsaW5lIHNlcGFyYXRlbHkuDQpgYGB7cn0NCnN0cl9leHRyYWN0X2FsbCh4LCByZWdleCgiXkxpbmUiLCBtdWx0aWxpbmUgPSBUUlVFKSlbWzFdXQ0KYGBgDQpUYXNrLTU6Q3JlYXRpbmcgYSByZWd1bGFyIGV4cHJlc3Npb24gcGF0dGVybiBmb3IgcGhvbmUgbnVtYmVycywgYWxsb3dpbmcgZm9yIHZhcmlhdGlvbnMgaW4gZm9ybWF0dGluZywgYW5kIGF0dGVtcHRpbmcgdG8gbWF0Y2ggaXQgYWdhaW5zdCB0aGUgcHJvdmlkZWQgcGhvbmUgbnVtYmVyLg0KYGBge3J9DQpwaG9uZSA8LSByZWdleCgiDQogIFxcKD8gICAgICMgb3B0aW9uYWwgb3BlbmluZyBwYXJlbnMNCiAgKFxcZHszfSkgIyBhcmVhIGNvZGUNCiAgWykgLV0/ICAgIyBvcHRpb25hbCBjbG9zaW5nIHBhcmVucywgc3BhY2UsIG9yIGRhc2gNCiAgKFxcZHszfSkgIyBhbm90aGVyIHRocmVlIG51bWJlcnMNCiAgWyAtXT8gICAgIyBvcHRpb25hbCBzcGFjZSBvciBkYXNoDQogIChcXGR7M30pICMgdGhyZWUgbW9yZSBudW1iZXJzDQogICIsIGNvbW1lbnRzID0gVFJVRSkNCg0Kc3RyX21hdGNoKCI1MTQtNzkxLTgxNDEiLCBwaG9uZSkNCmBgYA0KVGFzay02Okluc3RhbGxsaW5nIHRoZSBwYWNrYWdlIGFuZCBCZW5jaG1hcmtpbmcgc3RyaW5nIGRldGVjdGlvbiBpbiAic2VudGVuY2VzIiB1c2luZyBmaXhlZCBhbmQgcmVnZXggcGF0dGVybnMgMjAgdGltZXMgZWFjaCwgY29tcGFyaW5nIHBlcmZvcm1hbmNlIHdpdGggbWljcm9iZW5jaG1hcmsuDQpgYGB7cn0NCg0KcGFja2FnZV90b19pbnN0YWxsIDwtIGMoIm1pY3JvYmVuY2htYXJrIikNCg0KZm9yIChwYWNrYWdlX25hbWUgaW4gcGFja2FnZV90b19pbnN0YWxsKSB7DQogIGlmICghcmVxdWlyZU5hbWVzcGFjZShwYWNrYWdlX25hbWUsIHF1aWV0bHkgPSBUUlVFKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMocGFja2FnZV9uYW1lKQ0KICB9DQp9DQpsaWJyYXJ5KG1pY3JvYmVuY2htYXJrKQ0KDQptaWNyb2JlbmNobWFyazo6bWljcm9iZW5jaG1hcmsoDQogIGZpeGVkID0gc3RyX2RldGVjdChzZW50ZW5jZXMsIGZpeGVkKCJ0aGUiKSksDQogIHJlZ2V4ID0gc3RyX2RldGVjdChzZW50ZW5jZXMsICJ0aGUiKSwNCiAgdGltZXMgPSAyMA0KICApDQpgYGANClRhc2stNzpTdGFydGluZyB3aXRoIGExIGJlaW5nICJcdTAwZTEiIGFuZCBhMiBiZWluZyAiYVx1MDMwMSIsIGJvdGggcmVwcmVzZW50aW5nIHRoZSBjaGFyYWN0ZXIgIsOhIiwgdGhleSBhcmUgY29tcGFyZWQgZm9yIGVxdWFsaXR5Lg0KYGBge3J9DQphMSA8LSAiXHUwMGUxIg0KYTIgPC0gImFcdTAzMDEiDQpjKGExLCBhMikNCmExID09IGEyDQpgYGANClRhc2stODogQ2hlY2tpbmcgaWYgYGExYCBjb250YWlucyB0aGUgZml4ZWQgc3RyaW5nIGBhMmAgcmV0dXJucyBgRkFMU0VgLCB3aGVyZWFzIHVzaW5nIGNvbGxhdGlvbiBydWxlcyByZXR1cm5zIGBUUlVFYC4NCmBgYHtyfQ0Kc3RyX2RldGVjdChhMSwgZml4ZWQoYTIpKQ0KDQpzdHJfZGV0ZWN0KGExLCBjb2xsKGEyKSkNCmBgYA0KVGFzay05OkNyZWF0aW5nIGEgdmVjdG9yIGBpYCB3aXRoIGRpZmZlcmVudCBmb3JtcyBvZiB0aGUgbGV0dGVyICJpIiwgdGhlbiB1c2luZyBgc3RyX3N1YnNldGAgdG8gZmlsdGVyIHRoZW0gYmFzZWQgb24gY29sbGF0aW9uLg0KYGBge3J9DQppIDwtIGMoIkkiLCAixLAiLCAiaSIsICLEsSIpDQppDQpzdHJfc3Vic2V0KGksIGNvbGwoImkiLCBpZ25vcmVfY2FzZSA9IFRSVUUpKQ0Kc3RyX3N1YnNldChpLCBjb2xsKCJpIiwgaWdub3JlX2Nhc2UgPSBUUlVFLCBsb2NhbGUgPSAidHIiKSkNCmBgYA0KDQpUYXNrLTEwOiBGZXRjaGluZyBsb2NhbGUgaW5mb3JtYXRpb24uDQpgYGB7cn0NCnN0cmluZ2k6OnN0cmlfbG9jYWxlX2luZm8oKQ0KDQpgYGANClRhc2stMTE6VmlzdWFsaXppbmcgd29yZCBib3VuZGFyaWVzIGFuZCBleHRyYWN0cyBhbGwgd29yZHMgZnJvbSB0aGUgc3RyaW5nLg0KYGBge3J9DQp4IDwtICJUaGlzIGlzIGEgc2VudGVuY2UuIg0Kc3RyX3ZpZXdfYWxsKHgsIGJvdW5kYXJ5KCJ3b3JkIikpDQpzdHJfZXh0cmFjdF9hbGwoeCwgYm91bmRhcnkoIndvcmQiKSkNCmBgYA0KIyBDSC0xNTogRmFjdG9ycw0KIyMgQ3JlYXRpZyBmYWN0b3JzDQpUYXNrLTE6QWRkaW5nIGNoYXJhY3RlciB2ZWN0b3IgaW4gdmFyaWFibGUgeDENCmBgYHtyfQ0KeDEgPC0gYygiRGVjIiwgIkFwciIsICJKYW4iLCAiTWFyIikNCmBgYA0KDQpUYXNrLTI6QWRkaW5nIGNoYXJhY3RlciB2ZWN0b3IgaW4gdmFyaWFibGUgeDINCmBgYHtyfQ0KeDIgPC0gYygiRGVjIiwgIkFwciIsICJKYW0iLCAiTWFyIikNCmBgYA0KDQpUYXNrLTM6U29ydGluZyBYMSANCmBgYHtyfQ0Kc29ydCh4MSkNCg0KYGBgDQpUYXNrLTQ6QWRkaW5nIENoYXJhY3RlciB2ZWN0b3IgaW4gbW9udGhfbGV2ZWxzDQpgYGB7cn0NCm1vbnRoX2xldmVscyA8LSBjKA0KICAiSmFuIiwgIkZlYiIsICJNYXIiLCAiQXByIiwgIk1heSIsICJKdW4iLCANCiAgIkp1bCIsICJBdWciLCAiU2VwIiwgIk9jdCIsICJOb3YiLCAiRGVjIg0KKQ0KYGBgDQoNClRhc2stNTpBc3NpZ25pbmcgdGhlIGZhY3RvciBsZXZlbHMgdG8gdGhlIHZhcmlhYmxlIHgxLCB1c2luZyB0aGUgcHJlZGVmaW5lZCBtb250aF9sZXZlbHMuDQpgYGB7cn0NCnkxIDwtIGZhY3Rvcih4MSwgbGV2ZWxzID0gbW9udGhfbGV2ZWxzKQ0KeQ0KYGBgDQpUYXNrLTY6U29ydGluZyB0aGUgZmFjdG9yIGxldmVscyBpbiB5MS4NCmBgYHtyfQ0Kc29ydCh5MSkNCmBgYA0KVGFzay03OmNyZWF0aW5nIGEgZmFjdG9yIHkyIGZyb20geDIgd2l0aCBjdXN0b20gbGV2ZWxzIHNwZWNpZmllZCBieSBtb250aF9sZXZlbHMuDQpgYGB7cn0NCnkyIDwtIGZhY3Rvcih4MiwgbGV2ZWxzID0gbW9udGhfbGV2ZWxzKQ0KeTINCmBgYA0KVGFzay04OnBhcnNpbmcgdGhlIHZhbHVlcyBpbiB4MiBhcyBmYWN0b3JzDQpgYGB7cn0NCnkyIDwtIHBhcnNlX2ZhY3Rvcih4MiwgbGV2ZWxzID0gbW9udGhfbGV2ZWxzKQ0KYGBgDQpUYXNrLTk6IG9taXR0aW5nIHRoZSBsZXZlbHMuDQpgYGB7cn0NCmZhY3Rvcih4MSkNCmBgYA0KVGFzay0xMDpDcmVhdGluZyBhIGZhY3RvciBmMSBmcm9tIHRoZSB2YWx1ZXMgaW4geDEsIHVzaW5nIHRoZSB1bmlxdWUgdmFsdWVzIG9mIHgxIGFzIGxldmVscy4NCmBgYHtyfQ0KZjEgPC0gZmFjdG9yKHgxLCBsZXZlbHMgPSB1bmlxdWUoeDEpKQ0KZjENCmBgYA0KVGFzay0xMTogY3JlYXRpbmcgYSBmYWN0b3IgZjIgZnJvbSB0aGUgdmFsdWVzIGluIHgxLCBvcmRlcmluZyB0aGVtIGFjY29yZGluZyB0byB0aGVpciBhcHBlYXJhbmNlIGluIHgxLg0KYGBge3J9DQpmMiA8LSB4MSAlPiUgZmFjdG9yKCkgJT4lIGZjdF9pbm9yZGVyKCkNCmYyDQpgYGANClRhc2stMTI6T21pdHRpbmcgbGV2ZWxzMg0KYGBge3J9DQpsZXZlbHMoZjIpDQpgYGANCiMgR2VuZXJhbCBTb2NpYWwgU3VydmV5DQpUYXNrLTE6TG9hZGluZyBkYXRhc2V0cw0KYGBge3J9DQpnc3NfY2F0DQpgYGANCg0KVGFzay0yOlNlZWluZyBsZXZlbHMgdGhyb3VnaCBjb3VudCgpDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIGNvdW50KHJhY2UpDQpgYGANClRhc2stMzpBbHNvIHNlZWluZyB0aHJvdWdoIGJhcigpDQpgYGB7cn0NCmdncGxvdChnc3NfY2F0LCBhZXMocmFjZSkpICsNCiAgZ2VvbV9iYXIoKQ0KYGBgDQoNClRhc2stNDpHZW5lcmF0aW5nIGEgYmFyIHBsb3QgdXNpbmcgZ2dwbG90KCkNCmBgYHtyfQ0KZ2dwbG90KGdzc19jYXQsYWVzKHJhY2UpKStnZW9tX2JhcigpK3NjYWxlX3hfZGlzY3JldGUoZHJvcD1GQUxTRSkNCmBgYA0KIyBNb2RpZnlpbmcgZmFjdG9yIG9yZGVyDQpUYXNrLTE6Y2FsY3VsYXRpbmcgc3VtbWFyeSBzdGF0aXN0aWNzIGFuZCB0aGVuIGNyZWF0aW5nIHNjYXR0ZXIgcGxvdCANCmBgYHtyfQ0KcmVsaWdfc3VtbWFyeSA8LSBnc3NfY2F0ICU+JQ0KICBncm91cF9ieShyZWxpZykgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhZ2UgPSBtZWFuKGFnZSwgbmEucm0gPSBUUlVFKSwNCiAgICB0dmhvdXJzID0gbWVhbih0dmhvdXJzLCBuYS5ybSA9IFRSVUUpLA0KICAgIG4gPSBuKCkNCiAgKQ0KDQpnZ3Bsb3QocmVsaWdfc3VtbWFyeSwgYWVzKHR2aG91cnMsIHJlbGlnKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQpUYXNrLTI6R2VuZXJhdGluZyBhIHNjYXR0ZXIgcGxvdCB1c2luZyBgZ2dwbG90YCwgd2hlcmUgdGhlIHgtYXhpcyByZXByZXNlbnRzIHRoZSBtZWFuIFRWIGhvdXJzIChgdHZob3Vyc2ApLCBhbmQgdGhlIHktYXhpcyByZXByZXNlbnRzIHRoZSBgcmVsaWdgIHZhcmlhYmxlIHJlb3JkZXJlZCBieSBtZWFuIFRWIGhvdXJzLg0KYGBge3J9DQpnZ3Bsb3QocmVsaWdfc3VtbWFyeSwgYWVzKHR2aG91cnMsIGZjdF9yZW9yZGVyKHJlbGlnLCB0dmhvdXJzKSkpICsNCiAgZ2VvbV9wb2ludCgpDQpgYGANClRhc2stMzpDcmVhdGluZyBhIHNjYXR0ZXIgcGxvdCB1c2luZyBnZ3Bsb3QuDQpgYGB7cn0NCnJlbGlnX3N1bW1hcnkgJT4lDQogIG11dGF0ZShyZWxpZyA9IGZjdF9yZW9yZGVyKHJlbGlnLCB0dmhvdXJzKSkgJT4lDQogIGdncGxvdChhZXModHZob3VycywgcmVsaWcpKSArDQogICAgZ2VvbV9wb2ludCgpDQpgYGANClRhc2stNDpHZW5lcmF0aW5nIGEgc2NhdHRlciBwbG90IHVzaW5nIGdncGxvdA0KYGBge3J9DQpyaW5jb21lX3N1bW1hcnkgPC0gZ3NzX2NhdCAlPiUNCiAgZ3JvdXBfYnkocmluY29tZSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhZ2UgPSBtZWFuKGFnZSwgbmEucm0gPSBUUlVFKSwNCiAgICB0dmhvdXJzID0gbWVhbih0dmhvdXJzLCBuYS5ybSA9IFRSVUUpLA0KICAgIG4gPSBuKCkNCiAgKQ0KDQpnZ3Bsb3QocmluY29tZV9zdW1tYXJ5LCBhZXMoYWdlLCBmY3RfcmVvcmRlcihyaW5jb21lLCBhZ2UpKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KVGFzay01OiBjcmVhdGVzIGEgc2NhdHRlciBwbG90IG9mIHRoZSBhdmVyYWdlIGFnZSBieSBpbmNvbWUgbGV2ZWwsIHdpdGggIk5vdCBhcHBsaWNhYmxlIiBhcyB0aGUgcmVmZXJlbmNlIGxldmVsIGZvciBpbmNvbWUNCmBgYHtyfQ0KZ2dwbG90KHJpbmNvbWVfc3VtbWFyeSwgYWVzKGFnZSwgZmN0X3JlbGV2ZWwocmluY29tZSwgIk5vdCBhcHBsaWNhYmxlIikpKSArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQpUYXNrLTY6Y2FsY3VsYXRpbmcgdGhlIHByb3BvcnRpb24gb2YgZWFjaCBtYXJpdGFsIHN0YXR1cyBncm91cCBhY3Jvc3MgZGlmZmVyZW50IGFnZSBncm91cHMgYW5kIGNyZWF0ZXMgYSBsaW5lIHBsb3Qgc2hvd2luZyB0aGUgZGlzdHJpYnV0aW9uIG9mIG1hcml0YWwgc3RhdHVzIHByb3BvcnRpb25zIGJ5IGFnZS4NCmBgYHtyfQ0KYnlfYWdlIDwtIGdzc19jYXQgJT4lDQogIGZpbHRlcighaXMubmEoYWdlKSkgJT4lDQogIGNvdW50KGFnZSwgbWFyaXRhbCkgJT4lDQogIGdyb3VwX2J5KGFnZSkgJT4lDQogIG11dGF0ZShwcm9wID0gbiAvIHN1bShuKSkNCg0KZ2dwbG90KGJ5X2FnZSwgYWVzKGFnZSwgcHJvcCwgY29sb3VyID0gbWFyaXRhbCkpICsNCiAgZ2VvbV9saW5lKG5hLnJtID0gVFJVRSkNCg0KZ2dwbG90KGJ5X2FnZSwgYWVzKGFnZSwgcHJvcCwgY29sb3VyID0gZmN0X3Jlb3JkZXIyKG1hcml0YWwsIGFnZSwgcHJvcCkpKSArDQogIGdlb21fbGluZSgpICsNCiAgbGFicyhjb2xvdXIgPSAibWFyaXRhbCIpDQpgYGANClRhc2stNzogQWRqdXN0aW5nIHRoZSBvcmRlciBvZiB0aGUgIm1hcml0YWwiIHZhcmlhYmxlIGJhc2VkIG9uIGZyZXF1ZW5jeSBhbmQgdGhlbiByZXZlcnNlcyB0aGUgb3JkZXIgYmVmb3JlIGdlbmVyYXRpbmcgYSBiYXIgcGxvdCBpbGx1c3RyYXRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBtYXJpdGFsIHN0YXR1cy4NCmBgYHtyfQ0KZ3NzX2NhdCAlPiUNCiAgbXV0YXRlKG1hcml0YWwgPSBtYXJpdGFsICU+JSBmY3RfaW5mcmVxKCkgJT4lIGZjdF9yZXYoKSkgJT4lDQogIGdncGxvdChhZXMobWFyaXRhbCkpICsNCiAgICBnZW9tX2JhcigpDQpgYGANCiMgTW9kaWZ5aW5nIGZhY3RvciBsZXZlbHMNCg0KVGFzay0xOiBjb3VudGluZyB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggdW5pcXVlIHZhbHVlIGluIHRoZSAicGFydHlpZCIgdmFyaWFibGUgb2YgdGhlICJnc3NfY2F0IiBkYXRhc2V0Lg0KYGBge3J9DQpnc3NfY2F0JT4lY291bnQocGFydHlpZCkNCmBgYA0KVGFzay0yOlJlY29yZGluZyB0aGUgbGV2ZWxzIG9mIHRoZSAicGFydHlpZCIgdmFyaWFibGUgaW4gdGhlICJnc3NfY2F0IiBkYXRhc2V0IGFuZCB0aGVuIGNvdW50cyB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggdW5pcXVlIHJlY29yZGVkIHZhbHVlLg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBtdXRhdGUoIHBhcnR5aWQ9ZmN0X3JlY29kZShwYXJ0eWlkLA0KICAgICJSZXB1YmxpY2FuLCBzdHJvbmciICAgID0gIlN0cm9uZyByZXB1YmxpY2FuIiwNCiAgICAiUmVwdWJsaWNhbiwgd2VhayIgICAgICA9ICJOb3Qgc3RyIHJlcHVibGljYW4iLA0KICAgICJJbmRlcGVuZGVudCwgbmVhciByZXAiID0gIkluZCxuZWFyIHJlcCIsDQogICAgIkluZGVwZW5kZW50LCBuZWFyIGRlbSIgPSAiSW5kLG5lYXIgZGVtIiwNCiAgICAiRGVtb2NyYXQsIHdlYWsiICAgICAgICA9ICJOb3Qgc3RyIGRlbW9jcmF0IiwNCiAgICAiRGVtb2NyYXQsIHN0cm9uZyIgICAgICA9ICJTdHJvbmcgZGVtb2NyYXQiDQogICAgKSklPiUNCiAgY291bnQocGFydHlpZCkNCmBgYA0KVGFzay0zOlJlY2F0ZWdvcml6aW5nIGFuZCBjb3VudGluZyBwYXJ0eSBhZmZpbGlhdGlvbnMgaW4gdGhlICJnc3NfY2F0IiBkYXRhc2V0Lg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBtdXRhdGUocGFydHlpZCA9IGZjdF9yZWNvZGUocGFydHlpZCwNCiAgICAiUmVwdWJsaWNhbiwgc3Ryb25nIiAgICA9ICJTdHJvbmcgcmVwdWJsaWNhbiIsDQogICAgIlJlcHVibGljYW4sIHdlYWsiICAgICAgPSAiTm90IHN0ciByZXB1YmxpY2FuIiwNCiAgICAiSW5kZXBlbmRlbnQsIG5lYXIgcmVwIiA9ICJJbmQsbmVhciByZXAiLA0KICAgICJJbmRlcGVuZGVudCwgbmVhciBkZW0iID0gIkluZCxuZWFyIGRlbSIsDQogICAgIkRlbW9jcmF0LCB3ZWFrIiAgICAgICAgPSAiTm90IHN0ciBkZW1vY3JhdCIsDQogICAgIkRlbW9jcmF0LCBzdHJvbmciICAgICAgPSAiU3Ryb25nIGRlbW9jcmF0IiwNCiAgICAiT3RoZXIiICAgICAgICAgICAgICAgICA9ICJObyBhbnN3ZXIiLA0KICAgICJPdGhlciIgICAgICAgICAgICAgICAgID0gIkRvbid0IGtub3ciLA0KICAgICJPdGhlciIgICAgICAgICAgICAgICAgID0gIk90aGVyIHBhcnR5Ig0KICApKSAlPiUNCiAgY291bnQocGFydHlpZCkNCmBgYA0KVGFzay00OiBDb2xsYXBzaW5nIGNhdGVnb3JpZXMgd2l0aGluIHRoZSAicGFydHlpZCIgdmFyaWFibGUgaW4gdGhlICJnc3NfY2F0IiBkYXRhc2V0IGludG8gYnJvYWRlciBncm91cHMgYW5kIHRoZW4gY291bnRpbmcgdGhlIGZyZXF1ZW5jeSBvZiBlYWNoIGNvbGxhcHNlZCBjYXRlZ29yeS4NCmBgYHtyfQ0KZ3NzX2NhdCU+JQ0KICBtdXRhdGUocGFydHlpZD1mY3RfY29sbGFwc2UocGFydHlpZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG90aGVyPWMoIk5vIGFuc3dlciIsICJEb24ndCBrbm93IiwgIk90aGVyIHBhcnR5IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXA9YygiU3Ryb25nIHJlcHVibGljYW4iLCAiTm90IHN0ciByZXB1YmxpY2FuIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmQ9YygiSW5kLG5lYXIgcmVwIiwgIkluZGVwZW5kZW50IiwgIkluZCxuZWFyIGRlbSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVtPWMoIk5vdCBzdHIgZGVtb2NyYXQiLCAiU3Ryb25nIGRlbW9jcmF0IikpKSAlPiUNCiAgY291bnQocGFydHlpZCkNCmBgYA0KDQpUYXNrLTU6Q291bnRpbmcgYW5kIGFnZ3JlZ2F0aW5nIHJlbGlnaW91cyBhZmZpbGlhdGlvbnMgaW4gdGhlICJnc3NfY2F0IiBkYXRhc2V0IGFmdGVyIGx1bXBpbmcgdG9nZXRoZXIgbGVzcyBmcmVxdWVudCBjYXRlZ29yaWVzLg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBtdXRhdGUocmVsaWcgPSBmY3RfbHVtcChyZWxpZykpICU+JQ0KICBjb3VudChyZWxpZykNCmBgYA0KVGFzay02OiJTdW1tYXJpemluZyByZWxpZ2lvdXMgYWZmaWxpYXRpb25zIGFmdGVyIGx1bXBpbmcgaW5mcmVxdWVudCBjYXRlZ29yaWVzIGFuZCBzb3J0LiINCmBgYHtyfQ0KZ3NzX2NhdCAlPiUNCiAgbXV0YXRlKHJlbGlnID0gZmN0X2x1bXAocmVsaWcsIG4gPSAxMCkpICU+JQ0KICBjb3VudChyZWxpZywgc29ydCA9IFRSVUUpICU+JQ0KICBwcmludChuID0gSW5mKQ0KYGBgDQoNCiMgQ0gtRGF0YSBhbmQgVGltZXMNCg0KVGFzay0xOkxvYWRpbmcgbGlicmFyeQ0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmBgYA0KDQojIyBDcmVhdGluZyBkYXRlcy90aW1lcw0KVGFzay0xOiBQcmludGluZyAgY3VycmVudCBkYXRlIG9yIGRhdGUtdGltZQ0KYGBge3J9DQp0b2RheSgpDQpub3coKQ0KYGBgDQojIyBGb3JtIHN0cmluZ3MNClRhc2stMjpjb252ZXJ0aW5nIGRhdGUgc3RyaW5ncyB0byBkYXRlIG9iamVjdHMgaW4gZGlmZmVyZW50IGZvcm1hdHMuDQpgYGB7cn0NCnltZCgiMjAxNy0wMS0zMSIpDQptZHkoIkphbnVhcnkgMzFzdCwgMjAxNyIpDQpkbXkoIjMxLUphbi0yMDE3IikNCmBgYA0KYGBge3J9DQp5bWQoMjAxNzAxMzEpDQpgYGANCg0KYGBge3J9DQp5bWRfaG1zKCIyMDE3LTAxLTMxIDIwOjExOjU5IikNCm1keV9obSgiMDEvMzEvMjAxNyAwODowMSIpDQpgYGANCg0KDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBzZWxlY3QoeWVhciwgbW9udGgsIGRheSwgaG91ciwgbWludXRlKQ0KZmxpZ2h0cyAlPiUgDQogIHNlbGVjdCh5ZWFyLCBtb250aCwgZGF5LCBob3VyLCBtaW51dGUpICU+JSANCiAgbXV0YXRlKGRlcGFydHVyZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciwgbW9udGgsIGRheSwgaG91ciwgbWludXRlKSkNCmBgYA0KVGFzazogQ3JlYXRpbmcgZGF0ZS10aW1lIG9iamVjdHMgZnJvbSBob3VyLW1pbnV0ZSB0aW1lIGRhdGEgaW4gdGhlICdmbGlnaHRzJyBkYXRhc2V0IGFuZCBmaWx0ZXJpbmcgb3V0IHJvd3Mgd2l0aCBtaXNzaW5nIGRlcGFydHVyZSBvciBhcnJpdmFsIHRpbWVzDQpgYGB7cn0NCm1ha2VfZGF0ZXRpbWVfMTAwIDwtIGZ1bmN0aW9uKHllYXIsIG1vbnRoLCBkYXksIHRpbWUpIHsNCiAgbWFrZV9kYXRldGltZSh5ZWFyLCBtb250aCwgZGF5LCB0aW1lICUvJSAxMDAsIHRpbWUgJSUgMTAwKQ0KfQ0KDQpmbGlnaHRzX2R0IDwtIGZsaWdodHMgJT4lIA0KICBmaWx0ZXIoIWlzLm5hKGRlcF90aW1lKSwgIWlzLm5hKGFycl90aW1lKSkgJT4lIA0KICBtdXRhdGUoDQogICAgZGVwX3RpbWUgPSBtYWtlX2RhdGV0aW1lXzEwMCh5ZWFyLCBtb250aCwgZGF5LCBkZXBfdGltZSksDQogICAgYXJyX3RpbWUgPSBtYWtlX2RhdGV0aW1lXzEwMCh5ZWFyLCBtb250aCwgZGF5LCBhcnJfdGltZSksDQogICAgc2NoZWRfZGVwX3RpbWUgPSBtYWtlX2RhdGV0aW1lXzEwMCh5ZWFyLCBtb250aCwgZGF5LCBzY2hlZF9kZXBfdGltZSksDQogICAgc2NoZWRfYXJyX3RpbWUgPSBtYWtlX2RhdGV0aW1lXzEwMCh5ZWFyLCBtb250aCwgZGF5LCBzY2hlZF9hcnJfdGltZSkNCiAgKSAlPiUgDQogIHNlbGVjdChvcmlnaW4sIGRlc3QsIGVuZHNfd2l0aCgiZGVsYXkiKSwgZW5kc193aXRoKCJ0aW1lIikpDQoNCmZsaWdodHNfZHQNCmBgYA0KVGFzazogUGxvdHRpbmcgdGhlIGZyZXF1ZW5jeSBvZiBmbGlnaHRzIG92ZXIgdGltZSB1c2luZyBkZXBhcnR1cmUgZGF0ZS10aW1lDQoNCmBgYHtyfQ0KZmxpZ2h0c19kdCAlPiUgDQogIGdncGxvdChhZXMoZGVwX3RpbWUpKSArIA0KICBnZW9tX2ZyZXFwb2x5KGJpbndpZHRoID0gODY0MDApIA0KYGBgDQpUYXNrOiBQbG90dGluZyB0aGUgZnJlcXVlbmN5IG9mIGZsaWdodHMgb3ZlciB0aW1lIGZvciBhIHNwZWNpZmljIHBlcmlvZCB1c2luZyBkZXBhcnR1cmUgZGF0ZS10aW1lDQoNCmBgYHtyfQ0KZmxpZ2h0c19kdCAlPiUgDQogIGZpbHRlcihkZXBfdGltZSA8IHltZCgyMDEzMDEwMikpICU+JSANCiAgZ2dwbG90KGFlcyhkZXBfdGltZSkpICsgDQogIGdlb21fZnJlcXBvbHkoYmlud2lkdGggPSA2MDApICMgNjAwIHMgPSAxMCBtaW51dGVzDQpgYGANClRhc2s6IHRvIGNvbnZlcnQgdG9kYXkncyBkYXRlIHRvIGRhdGUtdGltZSBvYmplY3QNCmBgYHtyfQ0KYXNfZGF0ZXRpbWUodG9kYXkoKSkNCg0KYXNfZGF0ZShub3coKSkNCg0KYXNfZGF0ZSgzNjUgKiAxMCArIDIpDQoNCmBgYA0KRGF0ZS10aW1lIGNvbXBvbmVudHMNClRhc2s6IEV4dHJhY3RpbmcgdmFyaW91cyBjb21wb25lbnRzIG9mIGEgZGF0ZS10aW1lIG9iamVjdA0KYGBge3J9DQpkYXRldGltZSA8LSB5bWRfaG1zKCIyMDE2LTA3LTA4IDEyOjM0OjU2IikNCnllYXIoZGF0ZXRpbWUpDQptb250aChkYXRldGltZSkNCm1kYXkoZGF0ZXRpbWUpDQp5ZGF5KGRhdGV0aW1lKQ0Kd2RheShkYXRldGltZSkNCm1vbnRoKGRhdGV0aW1lLCBsYWJlbCA9IFRSVUUpDQp3ZGF5KGRhdGV0aW1lLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBGQUxTRSkNCmBgYA0KVGFzazogUGxvdHRpbmcgdGhlIGZyZXF1ZW5jeSBvZiBmbGlnaHRzIGJ5IGRheSBvZiB0aGUgd2Vlaw0KYGBge3J9DQpmbGlnaHRzX2R0ICU+JSANCiAgbXV0YXRlKHdkYXkgPSB3ZGF5KGRlcF90aW1lLCBsYWJlbCA9IFRSVUUpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHdkYXkpKSArDQogICAgZ2VvbV9iYXIoKQ0KYGBgDQpUYXNrOiBQbG90dGluZyBhdmVyYWdlIGRlbGF5IGJ5IG1pbnV0ZSBvZiBkZXBhcnR1cmUgdGltZQ0KYGBge3J9DQpmbGlnaHRzX2R0ICU+JSANCiAgbXV0YXRlKG1pbnV0ZSA9IG1pbnV0ZShkZXBfdGltZSkpICU+JSANCiAgZ3JvdXBfYnkobWludXRlKSAlPiUgDQogIHN1bW1hcmlzZSgNCiAgICBhdmdfZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKSwNCiAgICBuID0gbigpKSAlPiUgDQogIGdncGxvdChhZXMobWludXRlLCBhdmdfZGVsYXkpKSArDQogICAgZ2VvbV9saW5lKCkNCmBgYA0KVGFzazogUGxvdHRpbmcgYXZlcmFnZSBkZWxheSBieSBtaW51dGUgb2Ygc2NoZWR1bGVkIGRlcGFydHVyZSB0aW1lDQoNCmBgYHtyfQ0Kc2NoZWRfZGVwIDwtIGZsaWdodHNfZHQgJT4lIA0KICBtdXRhdGUobWludXRlID0gbWludXRlKHNjaGVkX2RlcF90aW1lKSkgJT4lIA0KICBncm91cF9ieShtaW51dGUpICU+JSANCiAgc3VtbWFyaXNlKA0KICAgIGF2Z19kZWxheSA9IG1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpLA0KICAgIG4gPSBuKCkpDQoNCmdncGxvdChzY2hlZF9kZXAsIGFlcyhtaW51dGUsIGF2Z19kZWxheSkpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KVGFzazogUGxvdHRpbmcgdGhlIG51bWJlciBvZiBmbGlnaHRzIGJ5IG1pbnV0ZSBvZiBzY2hlZHVsZWQgZGVwYXJ0dXJlIHRpbWUNCmBgYHtyfQ0KZ2dwbG90KHNjaGVkX2RlcCwgYWVzKG1pbnV0ZSwgbikpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KUm91bmRpbmcNClRhc2s6UGxvdHRpbmcgdGhlIG51bWJlciBvZiBmbGlnaHRzIGJ5IHdlZWssIHJvdW5kaW5nIHRvIHRoZSBuZWFyZXN0IHdlZWsNCg0KYGBge3J9DQpmbGlnaHRzX2R0ICU+JSANCiAgY291bnQod2VlayA9IGZsb29yX2RhdGUoZGVwX3RpbWUsICJ3ZWVrIikpICU+JSANCiAgZ2dwbG90KGFlcyh3ZWVrLCBuKSkgKw0KICAgIGdlb21fbGluZSgpDQoNCmBgYA0Kc2V0dGluZyBjb21wb3VuZHMNClRhc2s6IFNldHRpbmcgdXAgYSBkYXRlLXRpbWUgb2JqZWN0DQpgYGB7cn0NCihkYXRldGltZSA8LSB5bWRfaG1zKCIyMDE2LTA3LTA4IDEyOjM0OjU2IikpDQp5ZWFyKGRhdGV0aW1lKSA8LSAyMDIwDQpkYXRldGltZQ0KbW9udGgoZGF0ZXRpbWUpIDwtIDAxDQpkYXRldGltZQ0KaG91cihkYXRldGltZSkgPC0gaG91cihkYXRldGltZSkgKyAxDQpkYXRldGltZQ0KdXBkYXRlKGRhdGV0aW1lLCB5ZWFyID0gMjAyMCwgbW9udGggPSAyLCBtZGF5ID0gMiwgaG91ciA9IDIpDQpgYGANCmBgYHtyfQ0KeW1kKCIyMDE1LTAyLTAxIikgJT4lIA0KICB1cGRhdGUobWRheSA9IDMwKQ0KeW1kKCIyMDE1LTAyLTAxIikgJT4lIA0KICB1cGRhdGUoaG91ciA9IDQwMCkNCmBgYA0KVGFzazogQ3JlYXRpbmcgYSBuZXcgdmFyaWFibGUgJ2RlcF9ob3VyJyBieSB1cGRhdGluZyB0aGUgJ2RlcF90aW1lJyB0byB0aGUgZmlyc3QgZGF5IG9mIHRoZSB5ZWFyDQoNCmBgYHtyfQ0KZmxpZ2h0c19kdCAlPiUgDQogIG11dGF0ZShkZXBfaG91ciA9IHVwZGF0ZShkZXBfdGltZSwgeWRheSA9IDEpKSAlPiUgDQogIGdncGxvdChhZXMoZGVwX2hvdXIpKSArDQogICAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDMwMCkNCmBgYA0KVGltZSBTcGFucw0KQ29tcHV0ZSB0aGUgYWdlIG9mIGEgcGVyc29uIGJhc2VkIG9uIHRoZWlyIGJpcnRoZGF0ZSBhbmQgdG9kYXkncyBkYXRlDQpgYGB7cn0NCmhfYWdlIDwtIHRvZGF5KCkgLSB5bWQoMTk3OTEwMTQpDQpoX2FnZQ0KYXMuZHVyYXRpb24oaF9hZ2UpDQpgYGANCmBgYHtyfQ0KZHNlY29uZHMoMTUpDQpkbWludXRlcygxMCkNCmRob3VycyhjKDEyLCAyNCkpDQpkZGF5cygwOjUpDQpkd2Vla3MoMykNCmR5ZWFycygxKQ0KYGBgDQpgYGB7cn0NCjIgKiBkeWVhcnMoMSkNCmR5ZWFycygxKSArIGR3ZWVrcygxMikgKyBkaG91cnMoMTUpDQp0b21vcnJvdyA8LSB0b2RheSgpICsgZGRheXMoMSkNCmxhc3RfeWVhciA8LSB0b2RheSgpIC0gZHllYXJzKDEpDQpvbmVfcG0gPC0geW1kX2htcygiMjAxNi0wMy0xMiAxMzowMDowMCIsIHR6ID0gIkFtZXJpY2EvTmV3X1lvcmsiKQ0Kb25lX3BtDQpvbmVfcG0gKyBkZGF5cygxKQ0KYGBgDQpQZXJpb2RzDQpDcmVhdGUgcGVyaW9kIG9iamVjdHMgcmVwcmVzZW50aW5nIGRpZmZlcmVudCB0aW1lIHNwYW5zIGFuZCBQZXJmb3JtIGFyaXRobWV0aWMgb3BlcmF0aW9ucyB3aXRoIHBlcmlvZCBvYmplY3RzDQpgYGB7cn0NCm9uZV9wbQ0Kb25lX29tID0gZGF5cygxKQ0KYGBgDQpgYGB7cn0NCnNlY29uZHMoMTUpDQptaW51dGVzKDEwKQ0KaG91cnMoYygxMiwgMjQpKQ0KZGF5cyg3KQ0KbW9udGhzKDE6NikNCndlZWtzKDMpDQp5ZWFycygxKQ0KYGBgDQoNCmBgYHtyfQ0KMTAgKiAobW9udGhzKDYpICsgZGF5cygxKSkNCmRheXMoNTApICsgaG91cnMoMjUpICsgbWludXRlcygyKQ0KYGBgDQoNCmBgYHtyfQ0KeW1kKCIyMDE2LTAxLTAxIikgKyBkeWVhcnMoMSkNCnltZCgiMjAxNi0wMS0wMSIpICsgeWVhcnMoMSkNCm9uZV9wbSArIGRkYXlzKDEpDQpvbmVfcG0gKyBkYXlzKDEpDQpgYGANCg0KRmlsdGVyIGZsaWdodHMgd2hlcmUgYXJyaXZhbCB0aW1lIGlzIGJlZm9yZSBkZXBhcnR1cmUgdGltZQ0KYGBge3J9DQpmbGlnaHRzX2R0ICU+JSANCiAgZmlsdGVyKGFycl90aW1lIDwgZGVwX3RpbWUpIA0KYGBgDQoNClVwZGF0ZSBmbGlnaHRzIGRhdGEgdG8gY29ycmVjdCBvdmVybmlnaHQgZmxpZ2h0cw0KYGBge3J9DQpmbGlnaHRzX2R0IDwtIGZsaWdodHNfZHQgJT4lIA0KICBtdXRhdGUoDQogICAgb3Zlcm5pZ2h0ID0gYXJyX3RpbWUgPCBkZXBfdGltZSwNCiAgICBhcnJfdGltZSA9IGFycl90aW1lICsgZGF5cyhvdmVybmlnaHQgKiAxKSwNCiAgICBzY2hlZF9hcnJfdGltZSA9IHNjaGVkX2Fycl90aW1lICsgZGF5cyhvdmVybmlnaHQgKiAxKQ0KICApDQpgYGANCg0KRmlsdGVyIGZsaWdodHMgd2hlcmUgb3Zlcm5pZ2h0IGNvbmRpdGlvbiBpcyB0cnVlIGFuZCBhcnJpdmFsIHRpbWUgaXMgYmVmb3JlIGRlcGFydHVyZSB0aW1lDQpgYGB7cn0NCmZsaWdodHNfZHQgJT4lIA0KICBmaWx0ZXIob3Zlcm5pZ2h0LCBhcnJfdGltZSA8IGRlcF90aW1lKSANCmBgYA0KDQpJbnRlcnZhbHMNCkNhbGN1bGF0ZSB0aGUgcmF0aW8gb2Ygb25lIHllYXIgaW4gZGF5cw0KYGBge3J9DQp5ZWFycygxKSAvIGRheXMoMSkNCm5leHRfeWVhciA8LSB0b2RheSgpICsgeWVhcnMoMSkNCih0b2RheSgpICUtLSUgbmV4dF95ZWFyKSAvIGRkYXlzKDEpDQoodG9kYXkoKSAlLS0lIG5leHRfeWVhcikgJS8lIGRheXMoMSkNCmBgYA0KDQpEaXNwbGF5IHRpbWUgem9uZSBpbmZvcm1hdGlvbg0KYGBge3J9DQpTeXMudGltZXpvbmUoKQ0KbGVuZ3RoKE9sc29uTmFtZXMoKSkNCmhlYWQoT2xzb25OYW1lcygpKQ0KYGBgDQpgYGB7cn0NCih4MSA8LSB5bWRfaG1zKCIyMDE1LTA2LTAxIDEyOjAwOjAwIiwgdHogPSAiQW1lcmljYS9OZXdfWW9yayIpKQ0KKHgyIDwtIHltZF9obXMoIjIwMTUtMDYtMDEgMTg6MDA6MDAiLCB0eiA9ICJFdXJvcGUvQ29wZW5oYWdlbiIpKQ0KKHgzIDwtIHltZF9obXMoIjIwMTUtMDYtMDIgMDQ6MDA6MDAiLCB0eiA9ICJQYWNpZmljL0F1Y2tsYW5kIikpDQpgYGANCmBgYHtyfQ0KeDEgLSB4Mg0KeDEgLSB4Mw0KYGBgDQojIFBpcGVzDQoNClRhc2s6IFRvIGltcG9ydCB0aGUgcmVxdWlyZWQgbGlicmFyeQ0KYGBge3J9DQpwYWNrYWdlc190b19pbnN0YWxsIDwtIGMoInRpZHl2ZXJzZSIsICJwcnlyIikNCmZvciAocGFja2FnZV9uYW1lIGluIHBhY2thZ2VzX3RvX2luc3RhbGwpIHsNCiAgaWYgKCFyZXF1aXJlTmFtZXNwYWNlKHBhY2thZ2VfbmFtZSwgcXVpZXRseSA9IFRSVUUpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlX25hbWUpDQogIH0NCiAgbGlicmFyeShwYWNrYWdlX25hbWUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCg0KbGlicmFyeShtYWdyaXR0cikNCmBgYA0KDQpDcmVhdGUgZGlhbW9uZCBkYXRhIGFuZCBjYWxjdWxhdGUgdGhlIG9iamVjdCBzaXplcw0KYGBge3J9DQpkaWFtb25kcyA8LSBnZ3Bsb3QyOjpkaWFtb25kcw0KZGlhbW9uZHMyIDwtIGRpYW1vbmRzICU+JSANCiAgZHBseXI6Om11dGF0ZShwcmljZV9wZXJfY2FyYXQgPSBwcmljZSAvIGNhcmF0KQ0KDQpwcnlyOjpvYmplY3Rfc2l6ZShkaWFtb25kcykNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzMikNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzLCBkaWFtb25kczIpDQpgYGANCkZ1bmN0aW9ucw0KTm9ybWFsaXplIHRoZSBjb2x1bW5zIG9mIGEgZGF0YSBmcmFtZQ0KYGBge3J9DQpkZiA8LSB0aWJibGU6OnRpYmJsZSgNCiAgYSA9IHJub3JtKDEwKSwNCiAgYiA9IHJub3JtKDEwKSwNCiAgYyA9IHJub3JtKDEwKSwNCiAgZCA9IHJub3JtKDEwKQ0KKQ0KDQpkZiRhIDwtIChkZiRhIC0gbWluKGRmJGEsIG5hLnJtID0gVFJVRSkpIC8gDQogIChtYXgoZGYkYSwgbmEucm0gPSBUUlVFKSAtIG1pbihkZiRhLCBuYS5ybSA9IFRSVUUpKQ0KZGYkYiA8LSAoZGYkYiAtIG1pbihkZiRiLCBuYS5ybSA9IFRSVUUpKSAvIA0KICAobWF4KGRmJGIsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGYkYSwgbmEucm0gPSBUUlVFKSkNCmRmJGMgPC0gKGRmJGMgLSBtaW4oZGYkYywgbmEucm0gPSBUUlVFKSkgLyANCiAgKG1heChkZiRjLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRmJGMsIG5hLnJtID0gVFJVRSkpDQpkZiRkIDwtIChkZiRkIC0gbWluKGRmJGQsIG5hLnJtID0gVFJVRSkpIC8gDQogIChtYXgoZGYkZCwgbmEucm0gPSBUUlVFKSAtIG1pbihkZiRkLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQpOb3JtYWxpemUgYSBzaW5nbGUgY29sdW1uIG9mIGEgZGF0YSBmcmFtZQ0KYGBge3J9DQooZGYkYSAtIG1pbihkZiRhLCBuYS5ybSA9IFRSVUUpKSAvDQogIChtYXgoZGYkYSwgbmEucm0gPSBUUlVFKSAtIG1pbihkZiRhLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQpgYGB7cn0NCnggPC0gZGYkYQ0KKHggLSBtaW4oeCwgbmEucm0gPSBUUlVFKSkgLyAobWF4KHgsIG5hLnJtID0gVFJVRSkgLSBtaW4oeCwgbmEucm0gPSBUUlVFKSkNCmBgYA0KYGBge3J9DQpybmcgPC0gcmFuZ2UoeCwgbmEucm0gPSBUUlVFKQ0KKHggLSBybmdbMV0pIC8gKHJuZ1syXSAtIHJuZ1sxXSkNCmBgYA0KYGBge3J9DQpyZXNjYWxlMDEgPC0gZnVuY3Rpb24oeCkgew0KICBybmcgPC0gcmFuZ2UoeCwgbmEucm0gPSBUUlVFKQ0KICAoeCAtIHJuZ1sxXSkgLyAocm5nWzJdIC0gcm5nWzFdKQ0KfQ0KcmVzY2FsZTAxKGMoMCwgNSwgMTApKQ0KYGBgDQoNClJlc2NhbGUgYSB2ZWN0b3IgdG8gdGhlIHJhbmdlIFswLCAxXQ0KYGBge3J9DQpyZXNjYWxlMDEoYygtMTAsIDAsIDEwKSkNCnJlc2NhbGUwMShjKDEsIDIsIDMsIE5BLCA1KSkNCmBgYA0KDQpSZXNjYWxlIGVhY2ggY29sdW1uIG9mIGEgRGF0YUZyYW1lIHRvIHRoZSByYW5nZSBbMCwgMV0NCmBgYHtyfQ0KZGYkYSA8LSByZXNjYWxlMDEoZGYkYSkNCmRmJGIgPC0gcmVzY2FsZTAxKGRmJGIpDQpkZiRjIDwtIHJlc2NhbGUwMShkZiRjKQ0KZGYkZCA8LSByZXNjYWxlMDEoZGYkZCkNCmBgYA0KYGBge3J9DQp4IDwtIGMoMToxMCwgSW5mKQ0KcmVzY2FsZTAxKHgpDQpgYGANCg0KRGVmaW5lIHRoZSByZXNjYWxlMDEgZnVuY3Rpb24gYW5kIGFwcGx5IGl0DQpgYGB7cn0NCnJlc2NhbGUwMSA8LSBmdW5jdGlvbih4KSB7DQogIHJuZyA8LSByYW5nZSh4LCBuYS5ybSA9IFRSVUUsIGZpbml0ZSA9IFRSVUUpDQogICh4IC0gcm5nWzFdKSAvIChybmdbMl0gLSBybmdbMV0pDQp9DQpyZXNjYWxlMDEoeCkNCmBgYA0KDQpMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBhbmQgcGFja2FnZXMNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHB1cnJyKQ0KbGlicmFyeShtYWdyaXR0cikNCg0KIyBpbnN0YWxsLnBhY2thZ2VzKCJwcnlyIikNCmxpYnJhcnkocHJ5cikNCmBgYA0KDQojIyAxOC4yIFBpcGluZyBhbHRlcm5hdGl2ZXMNClRoaXMgaXMgYSBwb3B1bGFyIENoaWxkcmVu4oCZcyBwb2VtIHRoYXQgaXMgYWNjb21wYW5pZWQgYnkgaGFuZCBhY3Rpb25zLldl4oCZbGwgc3RhcnQgYnkgZGVmaW5pbmcgYW4gb2JqZWN0IHRvIHJlcHJlc2VudCBsaXR0bGUgYnVubnkgRm9vIEZvbzoNCg0KYGBge3IgMTguMi0xfQ0KIyBmb29fZm9vIDwtIGxpdHRsZV9idW5ueSgpDQpgYGANCiMjIyAxOC4yLjEgSW50ZXJtZWRpYXRlIHN0ZXBzDQpUaGUgc2ltcGxlc3QgYXBwcm9hY2ggaXMgdG8gc2F2ZSBlYWNoIHN0ZXAgYXMgYSBuZXcgb2JqZWN0Og0KDQpgYGB7ciAxOC4yLjEtMX0NCiMgZm9vX2Zvb18xIDwtIGhvcChmb29fZm9vLHRocm91Z2g9Zm9yZXN0KQ0KIyBmb29fZm9vXzIgPC0gc2Nvb3AoZm9vX2Zvb18xLCB1cCA9IGZpZWxkX21pY2UpDQojIGZvb19mb29fMyA8LSBib3AoZm9vX2Zvb18yLCBvbiA9IGhlYWQpDQpgYGANCg0KQ3JlYXRlIGRpYW1vbmRzIGRhdGFzZXQgYW5kIGNhbGN1bGF0ZSBwcmljZSBwZXIgY2FyYXQNCmBgYHtyIDE4LjIuMS0yfQ0KZGlhbW9uZHMgPC0gZ2dwbG90Mjo6ZGlhbW9uZHMNCmRpYW1vbmRzMiA8LSBkaWFtb25kcyAlPiUgDQogIGRwbHlyOjptdXRhdGUocHJpY2VfcGVyX2NhcmF0PXByaWNlL2NhcmF0KQ0KDQpwcnlyOjpvYmplY3Rfc2l6ZShkaWFtb25kcykNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzMikNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzLGRpYW1vbmRzMikNCmBgYA0KDQpJbnRyb2R1Y2UgTkEgdmFsdWUgaW50byBkaWFtb25kcyRjYXJhdCBhbmQgY2hlY2sgb2JqZWN0IHNpemVzDQpgYGB7ciAxOC4yLjEtM30NCmRpYW1vbmRzJGNhcmF0WzFdIDwtIE5BDQpwcnlyOjpvYmplY3Rfc2l6ZShkaWFtb25kcykNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzMikNCnByeXI6Om9iamVjdF9zaXplKGRpYW1vbmRzLGRpYW1vbmRzMikNCmBgYA0KDQojIyMgMTguMi4yIE92ZXJ3cml0ZSB0aGUgb3JpZ2luYWwNCkluc3RlYWQgb2YgY3JlYXRpbmcgaW50ZXJtZWRpYXRlIG9iamVjdHMgYXQgZWFjaCBzdGVwLCB3ZSBjb3VsZCBvdmVyd3JpdGUgdGhlIG9yaWdpbmFsIG9iamVjdDoNCmBgYHtyIDE4LjIuMi0xfQ0KIyBmb29fZm9vIDwtIGhvcChmb29fZm9vLCB0aHJvdWdoID0gZm9yZXN0KQ0KIyBmb29fZm9vIDwtIHNjb29wKGZvb19mb28sIHVwID0gZmllbGRfbWljZSkNCiMgZm9vX2ZvbyA8LSBib3AoZm9vX2Zvbywgb24gPSBoZWFkKQ0KYGBgDQojIyMgMTguMi4zIEZ1bmN0aW9uIGNvbXBvc2l0aW9uDQpBbm90aGVyIGFwcHJvYWNoIGlzIHRvIGFiYW5kb24gYXNzaWdubWVudCBhbmQganVzdCBzdHJpbmcgdGhlIGZ1bmN0aW9uIGNhbGxzIHRvZ2V0aGVyOg0KYGBge3IgMTguMi4zLTF9DQojIGJvcCgNCiMgICBzY29vcCgNCiMgICAgIGhvcChmb29fZm9vLCB0aHJvdWdoID0gZm9yZXN0KSwNCiMgICAgIHVwID0gZmllbGRfbWljZQ0KIyAgICksIA0KIyAgIG9uID0gaGVhZA0KIyApDQpgYGANCg0KSGVyZSB0aGUgZGlzYWR2YW50YWdlIGlzIHRoYXQgeW91IGhhdmUgdG8gcmVhZCBmcm9tIGluc2lkZS1vdXQsIGZyb20gcmlnaHQtdG8tbGVmdCwgYW5kIHRoYXQgdGhlIGFyZ3VtZW50cyBlbmQgdXAgc3ByZWFkIGZhciBhcGFydCAoZXZvY2F0aXZlbHkgY2FsbGVkIHRoZSBkYWd3b29kIHNhbmR3aGljaCBwcm9ibGVtKS4gSW4gc2hvcnQsIHRoaXMgY29kZSBpcyBoYXJkIGZvciBhIGh1bWFuIHRvIGNvbnN1bWUuDQoNCiMjIyAxOC4yLjQgVXNlIHRoZSBwaXBlDQpGaW5hbGx5LCB3ZSBjYW4gdXNlIHRoZSBwaXBlOg0KYGBge3IgMTguMi40LTF9DQojIGZvb19mb28gJT4lDQojICAgaG9wKHRocm91Z2ggPSBmb3Jlc3QpICU+JQ0KIyAgIHNjb29wKHVwID0gZmllbGRfbWljZSkgJT4lDQojICAgYm9wKG9uID0gaGVhZCkNCmBgYA0KDQpgYGB7ciAxOC4yLjQtMn0NCiMgbXlfcGlwZSA8LSBmdW5jdGlvbiguKSB7DQojICAgLiA8LSBob3AoLiwgdGhyb3VnaCA9IGZvcmVzdCkNCiMgICAuIDwtIHNjb29wKC4sIHVwID0gZmllbGRfbWljZSkNCiMgICBib3AoLiwgb24gPSBoZWFkKQ0KIyB9DQojIG15X3BpcGUoZm9vX2ZvbykNCmBgYA0KVEFTSzogIEZ1bmN0aW9ucyB0aGF0IHVzZSB0aGUgY3VycmVudCBlbnZpcm9ubWVudC4gRm9yIGV4YW1wbGUsIGBhc3NpZ24oKWAgd2lsbCBjcmVhdGUgYSBuZXcgdmFyaWFibGUgd2l0aCB0aGUgZ2l2ZW4gbmFtZSBpbiB0aGUgY3VycmVudCBlbnZpcm9ubWVudDoNCmBgYHtyIDE4LjIuNC0zfQ0KYXNzaWduKCJ4IiwxMCkNCngNCg0KIngiICU+JSBhc3NpZ24oMTAwKQ0KeA0KYGBgDQpBc3NpZ24gdmFsdWUgdG8gIngiIGluIHRoZSBzcGVjaWZpZWQgZW52aXJvbm1lbnQgYW5kIGNoZWNrIGl0cyB2YWx1ZSBhbmQgR2VuZXJhdGUgcmFuZG9tIG51bWJlcnMsIGNyZWF0ZSBhIG1hdHJpeCwgcGxvdCBpdCwgYW5kIGluc3BlY3QgaXRzIHN0cnVjdHVyZQ0KYGBge3IgMTguMi40LTR9DQplbnYgPC0gZW52aXJvbm1lbnQoKQ0KIngiICU+JSBhc3NpZ24oMTAwLGVudmlyPWVudikNCngNCmBgYA0KYGBge3IgMTguNC0xfQ0Kcm5vcm0oMTAwKSAlPiUgDQogIG1hdHJpeChuY29sPTIpICU+JSANCiAgcGxvdCgpICU+JSANCiAgc3RyKCkNCg0Kcm5vcm0oMTAwKSAlPiUgDQogIG1hdHJpeChuY29sPTIpICU+JSANCiAgcGxvdCgpICU+JSANCiAgc3RyKCkNCg0KbmRpc3QgPC0gcm5vcm0oMTAwMDAwKQ0KaGlzdChuZGlzdCkNCmBgYA0KDQpDYWxjdWxhdGUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdHdvIHZhcmlhYmxlcyBpbiBtdGNhcnMgZGF0YXNldA0KYGBge3IgMTguNC0yfQ0KbXRjYXJzICUkJQ0KICBjb3IoZGlzcCwgbXBnKQ0KYGBgDQoNCi0gRm9yIGFzc2lnbm1lbnQgbWFncml0dHIgcHJvdmlkZXMgdGhlIGAlPD4lYCBvcGVyYXRvciB3aGljaCBhbGxvd3MgeW91IHRvIHJlcGxhY2UgY29kZSBsaWtlOg0KYGBge3IgMTguNC0zfQ0KbXRjYXJzIDwtIG10Y2FycyAlPiUgDQogIHRyYW5zZm9ybShjeWw9Y3lsKjIpDQpgYGANCg0KDQpgYGB7ciAxOC40LTR9DQptdGNhcnMgJTw+JSB0cmFuc2Zvcm0oY3lsPWN5bCoyKQ0KYGBgDQojIENoYXB0ZXIgMTkgRnVuY3Rpb25zDQojIyAxOS4xIEludHJvZHVjdGlvbg0KDQojIyAxOS4yIFdoZW4gc2hvdWxkIHlvdSB3cml0ZSBhIGZ1bmN0aW9uPw0KYGBge3IgMTkuMi0xfQ0KZGYgPC0gdGliYmxlOjp0aWJibGUoDQogIGEgPSBybm9ybSgxMCksDQogIGIgPSBybm9ybSgxMCksDQogIGMgPSBybm9ybSgxMCksDQogIGQgPSBybm9ybSgxMCkNCikNCmRmDQoNCmRmJGEgPC0gKGRmJGEgLSBtaW4oZGYkYSwgbmEucm0gPSBUUlVFKSkgLyANCiAgKG1heChkZiRhLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRmJGEsIG5hLnJtID0gVFJVRSkpDQpkZiRiIDwtIChkZiRiIC0gbWluKGRmJGIsIG5hLnJtID0gVFJVRSkpIC8gDQogIChtYXgoZGYkYiwgbmEucm0gPSBUUlVFKSAtIG1pbihkZiRhLCBuYS5ybSA9IFRSVUUpKQ0KZGYkYyA8LSAoZGYkYyAtIG1pbihkZiRjLCBuYS5ybSA9IFRSVUUpKSAvIA0KICAobWF4KGRmJGMsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGYkYywgbmEucm0gPSBUUlVFKSkNCmRmJGQgPC0gKGRmJGQgLSBtaW4oZGYkZCwgbmEucm0gPSBUUlVFKSkgLyANCiAgKG1heChkZiRkLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRmJGQsIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KUmVzY2FsZSBhIHNpbmdsZSB2YXJpYWJsZSBpbiBhIGRhdGEgZnJhbWUNCmBgYHtyIDE5LjItMn0NCihkZiRhIC0gbWluKGRmJGEsIG5hLnJtID0gVFJVRSkpIC8NCiAgKG1heChkZiRhLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRmJGEsIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KUmVzY2FsZSBhIHNpbmdsZSB2YXJpYWJsZSB3aXRob3V0IGNyZWF0aW5nIGEgbmV3IG9iamVjdA0KYGBge3J9DQp4IDwtIGRmJGENCih4IC0gbWluKHgsIG5hLnJtID0gVCkpIC8gKG1heCh4LCBuYS5ybSA9IFQpLW1pbih4LCBuYS5ybSA9IFQpKQ0KYGBgDQoNClRhc2s6IFRoZXJlIGlzIHNvbWUgZHVwbGljYXRpb24gaW4gdGhpcyBjb2RlLiBXZeKAmXJlIGNvbXB1dGluZyB0aGUgcmFuZ2Ugb2YgdGhlIGRhdGEgdGhyZWUgdGltZXMsIHNvIGl0IG1ha2VzIHNlbnNlIHRvIGRvIGl0IGluIG9uZSBzdGVwOg0KYGBge3J9DQpybmcgPC0gcmFuZ2UoeCwgbmEucm0gPSBUKQ0KKHgtcm5nWzFdKS8ocm5nWzJdLXJuZ1sxXSkNCmBgYA0KDQpQdWxsaW5nIG91dCBpbnRlcm1lZGlhdGUgY2FsY3VsYXRpb25zIGludG8gbmFtZWQgdmFyaWFibGVzIGlzIGEgZ29vZCBwcmFjdGljZSBiZWNhdXNlIGl0IG1ha2VzIGl0IG1vcmUgY2xlYXIgd2hhdCB0aGUgY29kZSBpcyBkb2luZy4gTm93IHRoYXQgSeKAmXZlIHNpbXBsaWZpZWQgdGhlIGNvZGUsIGFuZCBjaGVja2VkIHRoYXQgaXQgc3RpbGwgd29ya3MsIEkgY2FuIHR1cm4gaXQgaW50byBhIGZ1bmN0aW9uOg0KYGBge3IgMTkuMi01fQ0KcmVzY2FsZTAxIDwtIGZ1bmN0aW9uKHgpew0KICBybmcgPC0gcmFuZ2UoeCwgbmEucm0gPSBUKQ0KICAoeC1ybmdbMV0pLyhybmdbMl0tcm5nWzFdKQ0KfQ0KcmVzY2FsZTAxKGMoMCw1LDEwKSkNCmBgYA0KDQpUZXN0IHRoZSByZXNjYWxlMDEgZnVuY3Rpb24gd2l0aCB2YXJpb3VzIGlucHV0cw0KYGBge3IgMTkuMi02fQ0KcmVzY2FsZTAxKGMoLTEwLDAsMTApKQ0KcmVzY2FsZTAxKGMoMSwyLDMsTkEsNSkpDQpgYGANCg0KV2UgY2FuIHNpbXBsaWZ5IHRoZSBvcmlnaW5hbCBleGFtcGxlIG5vdyB0aGF0IHdlIGhhdmUgYSBmdW5jdGlvbjoNCmBgYHtyIDE5LjItN30NCmRmJGEgPC0gcmVzY2FsZTAxKGRmJGEpDQpkZiRiIDwtIHJlc2NhbGUwMShkZiRiKQ0KZGYkYyA8LSByZXNjYWxlMDEoZGYkYykNCmRmJGQgPC0gcmVzY2FsZTAxKGRmJGQpDQpgYGANCg0KUmVzY2FsZSBhIHZlY3RvciB3aXRoIGluZmluaXRlIHZhbHVlcw0KYGBge3IgMTkuMi04fQ0KeCA8LSBjKDE6MTAsSW5mKQ0KcmVzY2FsZTAxKHgpDQpgYGANCg0KQmVjYXVzZSB3ZeKAmXZlIGV4dHJhY3RlZCB0aGUgY29kZSBpbnRvIGEgZnVuY3Rpb24sIHdlIG9ubHkgbmVlZCB0byBtYWtlIHRoZSBmaXggaW4gb25lIHBsYWNlOg0KYGBge3IgMTkuMi05fQ0KcmVzY2FsZTAxIDwtIGZ1bmN0aW9uKHgpew0KICBybmcgPC0gcmFuZ2UoeCxuYS5ybT1ULGZpbml0ZT1UKQ0KICAoeC1ybmdbMV0pLyhybmdbMl0tcm5nWzFdKQ0KfQ0KcmVzY2FsZTAxKHgpDQpgYGANCg0KIyMgMTkuNCBDb25kaXRpb25hbCBleGVjdXRpb24NCkFuIGBpZmAgc3RhdGVtZW50IGFsbG93cyB5b3UgdG8gY29uZGl0aW9uYWxseSBleGVjdXRlIGNvZGUuIA0KSXQgbG9va3MgbGlrZSB0aGlzOg0KYGBge3J9DQojIGlmIChjb25kaXRpb24pIHsNCiAgIyBjb2RlIGV4ZWN1dGVkIHdoZW4gY29uZGl0aW9uIGlzIFRSVUUNCiMgfSBlbHNlIHsNCiAgIyBjb2RlIGV4ZWN1dGVkIHdoZW4gY29uZGl0aW9uIGlzIEZBTFNFDQojIH0NCmBgYA0KDQpEZWZpbmUgYSBmdW5jdGlvbiB0byBjaGVjayBpZiBhbiBvYmplY3QgaGFzIG5hbWVzDQpgYGB7cn0NCmhhc19uYW1lIDwtIGZ1bmN0aW9uKHgpew0KICBubXMgPC0gbmFtZXMoeCkNCiAgaWYoaXMubnVsbChubXMpKXsNCiAgICByZXAoRkFMU0UsbGVuZ3RoKHgpKQ0KICB9ZWxzZSB7DQogICAgIWlzLm5hKG5tcykgJiBubXMgIT0iIg0KICB9DQp9DQpgYGANCiMjIyAxOS40LjEgQ29uZGl0aW9ucw0KaG93IGlmIGNvbmRpdGlvbiB3b3JrcyB3aXRoIHdhcm5pbmdzDQpgYGB7ciAxOS40LjEtMX0NCiMgaWYgKGMoVFJVRSxGQUxTRSkpe30NCiM+IFdhcm5pbmcgaW4gaWYgKGMoVFJVRSwgRkFMU0UpKSB7OiB0aGUgY29uZGl0aW9uIGhhcyBsZW5ndGggPiAxIGFuZCBvbmx5IHRoZQ0KIz4gZmlyc3QgZWxlbWVudCB3aWxsIGJlIHVzZWQNCiM+IE5VTEwNCg0KIyBpZiAoTkEpIHt9DQpgYGANCkNoZWNrIGlmIHR3byBvYmplY3RzIGFyZSBpZGVudGljYWwNCmBgYHtyfQ0KaWRlbnRpY2FsKDBMLDApDQp4IDwtIHNxcnQoMileMg0KeD09Mg0KeC0yDQpgYGANCg0KIyMjIDE5LjQuMiBNdWx0aXBsZSBjb25kaXRpb25zDQpZb3UgY2FuIGNoYWluIG11bHRpcGxlIGlmIHN0YXRlbWVudCB0b2dldGhlcjoNCmBgYHtyIDE5LjQuMi0xfQ0KIyBpZiAodGhpcykgew0KIyAgICMgZG8gdGhhdA0KIyB9IGVsc2UgaWYgKHRoYXQpIHsNCiMgICAjIGRvIHNvbWV0aGluZyBlbHNlDQojIH0gZWxzZSB7DQojICAgIyANCiMgfQ0KYGBgDQoNCmBgYHtyIDE5LjQuMi0yfQ0KIz4gZnVuY3Rpb24oeCwgeSwgb3ApIHsNCiM+ICAgc3dpdGNoKG9wLA0KIz4gICAgIHBsdXMgPSB4ICsgeSwNCiM+ICAgICBtaW51cyA9IHggLSB5LA0KIz4gICAgIHRpbWVzID0geCAqIHksDQojPiAgICAgZGl2aWRlID0geCAvIHksDQojPiAgICAgc3RvcCgiVW5rbm93biBvcCEiKQ0KIz4gICApDQojPiB9DQpgYGANCiMjIyAxOS40LjMgQ29kZSBzdHlsZQ0KDQpHb29kIHByYWN0aWNlIGZvciB3cml0aW5nIGlmIHN0YXRlbWVudHMNCmBgYHtyIDE5LjQuMy0xfQ0KIyBHb29kDQojIGlmICh5IDwgMCAmJiBkZWJ1Zykgew0KIyAgIG1lc3NhZ2UoIlkgaXMgbmVnYXRpdmUiKQ0KIyB9DQojIA0KIyBpZiAoeSA9PSAwKSB7DQojICAgbG9nKHgpDQojIH0gZWxzZSB7DQojICAgeSBeIHgNCiMgfQ0KIyANCiMgIyBCYWQNCiMgaWYgKHkgPCAwICYmIGRlYnVnKQ0KIyBtZXNzYWdlKCJZIGlzIG5lZ2F0aXZlIikNCiMgDQojIGlmICh5ID09IDApIHsNCiMgICBsb2coeCkNCiMgfSANCiMgZWxzZSB7DQojICAgeSBeIHgNCiMgfQ0KYGBgDQoNCkl04oCZcyBvayB0byBkcm9wIHRoZSBjdXJseSBicmFjZXMgaWYgeW91IGhhdmUgYSB2ZXJ5IHNob3J0IGlmIHN0YXRlbWVudCB0aGF0IGNhbiBmaXQgb24gb25lIGxpbmU6IA0KYGBge3IgMTkuNC4zLTJ9DQp5IDwtIDEwDQp4IDwtIGlmICh5IDwgMjApICJUb28gbG93IiBlbHNlICJUb28gaGlnaCINCmBgYA0KDQpJIHJlY29tbWVuZCB0aGlzIG9ubHkgZm9yIHZlcnkgYnJpZWYgYGlmYCBzdGF0ZW1lbnRzLiBPdGhlcndpc2UsIHRoZSBmdWxsIGZvcm0gaXMgZWFzaWVyIHRvIHJlYWQ6DQpgYGB7cn0NCmlmICh5IDwgMjApIHsNCiAgeCA8LSAiVG9vIGxvdyIgDQp9IGVsc2Ugew0KICB4IDwtICJUb28gaGlnaCINCn0NCmBgYA0KDQojIyAxOS41IEZ1bmN0aW9uIGFyZ3VtZW50cyANCg0KYGBge3J9DQojIENvbXB1dGUgY29uZmlkZW5jZSBpbnRlcnZhbCBhcm91bmQgbWVhbiB1c2luZyBub3JtYWwgYXBwcm94aW1hdGlvbg0KbWVhbl9jaSA8LSBmdW5jdGlvbih4LCBjb25mID0gMC45NSkgew0KICBzZSA8LSBzZCh4KSAvIHNxcnQobGVuZ3RoKHgpKQ0KICBhbHBoYSA8LSAxIC0gY29uZg0KICBtZWFuKHgpICsgc2UgKiBxbm9ybShjKGFscGhhIC8gMiwgMSAtIGFscGhhIC8gMikpDQp9DQoNCnggPC0gcnVuaWYoMTAwKQ0KbWVhbl9jaSh4KQ0KDQptZWFuX2NpKHgsIGNvbmYgPSAwLjk5KQ0KDQpgYGANCg0KDQojIyMgMTkuNS4xIENob29zaW5nIG5hbWVzDQojIyMgMTkuNS4yIENoZWtpbmcgdmFsdWVzDQpgYGB7cn0NCnd0X21lYW4gPC0gZnVuY3Rpb24oeCwgdykgew0KICBzdW0oeCAqIHcpIC8gc3VtKHcpDQp9DQp3dF92YXIgPC0gZnVuY3Rpb24oeCwgdykgew0KICBtdSA8LSB3dF9tZWFuKHgsIHcpDQogIHN1bSh3ICogKHggLSBtdSkgXiAyKSAvIHN1bSh3KQ0KfQ0Kd3Rfc2QgPC0gZnVuY3Rpb24oeCwgdykgew0KICBzcXJ0KHd0X3Zhcih4LCB3KSkNCn0NCmBgYA0KDQpXaGF0IGhhcHBlbnMgaWYgeCBhbmQgdyBhcmUgbm90IHRoZSBzYW1lIGxlbmd0aD8NCmBgYHtyfQ0Kd3RfbWVhbigxOjYsIDE6MykNCg0KYGBgDQoNCkluIHRoaXMgY2FzZSwgYmVjYXVzZSBvZiBS4oCZcyB2ZWN0b3IgcmVjeWNsaW5nIHJ1bGVzLCB3ZSBkb27igJl0IGdldCBhbiBlcnJvci4NCg0KSXTigJlzIGdvb2QgcHJhY3RpY2UgdG8gY2hlY2sgaW1wb3J0YW50IHByZWNvbmRpdGlvbnMsIGFuZCB0aHJvdyBhbiBlcnJvciAod2l0aCBgc3RvcCgpYCksIGlmIHRoZXkgYXJlIG5vdCB0cnVlOg0KYGBge3J9DQp3dF9tZWFuIDwtIGZ1bmN0aW9uKHgsIHcpIHsNCiAgaWYgKGxlbmd0aCh4KSAhPSBsZW5ndGgodykpIHsNCiAgICBzdG9wKCJgeGAgYW5kIGB3YCBtdXN0IGJlIHRoZSBzYW1lIGxlbmd0aCIsIGNhbGwuID0gRkFMU0UpDQogIH0NCiAgc3VtKHcgKiB4KSAvIHN1bSh3KQ0KfQ0KYGBgDQoNCmBgYHtyfQ0Kd3RfbWVhbiA8LSBmdW5jdGlvbih4LCB3LCBuYS5ybSA9IEZBTFNFKSB7DQogIGlmICghaXMubG9naWNhbChuYS5ybSkpIHsNCiAgICBzdG9wKCJgbmEucm1gIG11c3QgYmUgbG9naWNhbCIpDQogIH0NCiAgaWYgKGxlbmd0aChuYS5ybSkgIT0gMSkgew0KICAgIHN0b3AoImBuYS5ybWAgbXVzdCBiZSBsZW5ndGggMSIpDQogIH0NCiAgaWYgKGxlbmd0aCh4KSAhPSBsZW5ndGgodykpIHsNCiAgICBzdG9wKCJgeGAgYW5kIGB3YCBtdXN0IGJlIHRoZSBzYW1lIGxlbmd0aCIsIGNhbGwuID0gRkFMU0UpDQogIH0NCiAgDQogIGlmIChuYS5ybSkgew0KICAgIG1pc3MgPC0gaXMubmEoeCkgfCBpcy5uYSh3KQ0KICAgIHggPC0geFshbWlzc10NCiAgICB3IDwtIHdbIW1pc3NdDQogIH0NCiAgc3VtKHcgKiB4KSAvIHN1bSh3KQ0KfQ0KYGBgDQoNClRoaXMgaXMgYSBsb3Qgb2YgZXh0cmEgd29yayBmb3IgbGl0dGxlIGFkZGl0aW9uYWwgZ2Fpbi4gQSB1c2VmdWwgY29tcHJvbWlzZSBpcyB0aGUgYnVpbHQtaW4gYHN0b3BpZm5vdCgpYDogaXQgY2hlY2tzIHRoYXQgZWFjaCBhcmd1bWVudCBpcyBgVFJVRWAsIGFuZCBwcm9kdWNlcyBhIGdlbmVyaWMgZXJyb3IgbWVzc2FnZSBpZiBub3QuDQpgYGB7cn0NCnd0X21lYW4gPC0gZnVuY3Rpb24oeCwgdywgbmEucm0gPSBGQUxTRSkgew0KICBzdG9waWZub3QoaXMubG9naWNhbChuYS5ybSksIGxlbmd0aChuYS5ybSkgPT0gMSkNCiAgc3RvcGlmbm90KGxlbmd0aCh4KSA9PSBsZW5ndGgodykpDQogIA0KICBpZiAobmEucm0pIHsNCiAgICBtaXNzIDwtIGlzLm5hKHgpIHwgaXMubmEodykNCiAgICB4IDwtIHhbIW1pc3NdDQogICAgdyA8LSB3WyFtaXNzXQ0KICB9DQogIHN1bSh3ICogeCkgLyBzdW0odykNCn0NCmBgYA0KIyMjIDE5LjUuMyBEb3QtZG90LWRvdCguLi4pDQpNYW55IGZ1bmN0aW9ucyBpbiBSIHRha2UgYW4gYXJiaXRyYXJ5IG51bWJlciBvZiBpbnB1dHM6IA0KYGBge3IgMTkuNS4zLTF9DQpzdW0oMSwgMiwgMywgNCwgNSwgNiwgNywgOCwgOSwgMTApDQpzdHJpbmdyOjpzdHJfYygiYSIsICJiIiwgImMiLCAiZCIsICJlIiwgImYiKQ0KYGBgDQoNCkRlZmluZSBhIGZ1bmN0aW9uIHRvIGNvbmNhdGVuYXRlIHN0cmluZ3Mgd2l0aCBjb21tYXMNCmBgYHtyIDE5LjUuMy0yfQ0KY29tbWFzIDwtIGZ1bmN0aW9uKC4uLikgc3RyaW5ncjo6c3RyX2MoLi4uLCBjb2xsYXBzZSA9ICIsICIpDQpjb21tYXMobGV0dGVyc1sxOjEwXSkNCg0KDQpydWxlIDwtIGZ1bmN0aW9uKC4uLiwgcGFkID0gIi0iKSB7DQogIHRpdGxlIDwtIHBhc3RlMCguLi4pDQogIHdpZHRoIDwtIGdldE9wdGlvbigid2lkdGgiKSAtIG5jaGFyKHRpdGxlKSAtIDUNCiAgY2F0KHRpdGxlLCAiICIsIHN0cmluZ3I6OnN0cl9kdXAocGFkLCB3aWR0aCksICJcbiIsIHNlcCA9ICIiKQ0KfQ0KcnVsZSgiSW1wb3J0YW50IG91dHB1dCIpDQpgYGANCmBgYHtyfQ0KeCA8LSBjKDEsMikNCnN1bSh4LG5hLnJtPVQpDQpgYGANCg0KRGVmaW5lIGEgZnVuY3Rpb24gJ2NvbXBsaWNhdGVkX2Z1bmN0aW9uJyB3aXRoIGNvbmRpdGlvbnMgdG8gcmV0dXJuIDAgaWYgJ3gnIG9yICd5JyBpcyBlbXB0eQ0KYGBge3J9DQpjb21wbGljYXRlZF9mdW5jdGlvbiA8LSBmdW5jdGlvbih4LHkseil7DQogIGlmIChsZW50aCh4KT09MCB8fCBsZW5ndGgoeSk9PTApew0KICAgIHJldHVybigwKQ0KICB9DQp9DQpgYGANCg0KSW1wcm92ZSByZWFkYWJpbGl0eSBvZiBpZi1lbHNlIGJsb2NrcyBieSB1c2luZyBlYXJseSByZXR1cm4gZm9yIHNpbXBsZSBjYXNlcw0KYGBge3J9DQpmIDwtIGZ1bmN0aW9uKCkgew0KICBpZiAoeCkgew0KICAgICMgRG8gDQogICAgIyBzb21ldGhpbmcNCiAgICAjIHRoYXQNCiAgICAjIHRha2VzDQogICAgIyBtYW55DQogICAgIyBsaW5lcw0KICAgICMgdG8NCiAgICAjIGV4cHJlc3MNCiAgfSBlbHNlIHsNCiAgICAjIHJldHVybiBzb21ldGhpbmcgc2hvcnQNCiAgfQ0KfQ0KYGBgDQoNCkJ1dCBpZiB0aGUgZmlyc3QgYmxvY2sgaXMgdmVyeSBsb25nLCBieSB0aGUgdGltZSB5b3UgZ2V0IHRvIHRoZSBlbHNlLCB5b3XigJl2ZSBmb3Jnb3R0ZW4gdGhlIGNvbmRpdGlvbi4gT25lIHdheSB0byByZXdyaXRlIGl0IGlzIHRvIHVzZSBhbiBlYXJseSByZXR1cm4gZm9yIHRoZSBzaW1wbGUgY2FzZToNCmBgYHtyIDE5LjYuMS0zfQ0KZiA8LSBmdW5jdGlvbigpIHsNCiAgaWYgKCF4KSB7DQogICAgcmV0dXJuKHNvbWV0aGluZ19zaG9ydCkNCiAgfQ0KDQogICMgRG8gDQogICMgc29tZXRoaW5nDQogICMgdGhhdA0KICAjIHRha2VzDQogICMgbWFueQ0KICAjIGxpbmVzDQogICMgdG8NCiAgIyBleHByZXNzDQp9DQpgYGANCg0KVGhpcyB0ZW5kcyB0byBtYWtlIHRoZSBjb2RlIGVhc2llciB0byB1bmRlcnN0YW5kLCBiZWNhdXNlIHlvdSBkb24ndCBuZWVkIHF1aXRlIHNvIG11Y2ggY29udGV4dCB0byB1bmRlcnN0YW5kIGl0Lg0KDQojIyMgMTkuNi4yIFdyaXRpbmcgcGlwZWFibGUgZnVuY3Rpb25zDQpEZWZpbmUgYSBmdW5jdGlvbiB0byBzaG93IHRoZSBjb3VudCBvZiBtaXNzaW5nIHZhbHVlcyBpbiBhIGRhdGEgZnJhbWUNCmBgYHtyIDE5LjYuMi0xfQ0Kc2hvd19taXNzaW5nIDwtIGZ1bmN0aW9uKGRmKXsNCiAgbiA8LSBzdW0oaXMubmEoZGYpKQ0KICBjYXQoIk1pc3NpbmcgdmFsdWVzOiIsbiwiXG4iLHNlcD0iIikNCiAgDQogIGludmlzaWJsZShkZikNCn0NCmBgYA0KDQpJZiB3ZSBjYWxsIGl0IGludGVyYXRpdmVseSwgdGhlIGBpbnZpc2libGUoKWAgbWVhbnMgdGhhdCB0aGUgaW5wdXQgYGRmYCBkb2VzIG5vdCBnZXQgcHJpbnRlZCBvdXQ6IA0KYGBge3J9DQpzaG93X21pc3NpbmcobXRjYXJzKQ0KYGBgDQoNCkJ1dCBpdCdzIHN0aWxsIHRoZXJlLCBpdCdzIG5vdCBqdXN0IHByaW50ZWQgYnkgZGVmYXVsdDoNCmBgYHtyfQ0KeCA8LSBzaG93X21pc3NpbmcobXRjYXJzKQ0KY2xhc3MoeCkNCmRpbSh4KQ0KYGBgDQoNCkFuZCB3ZSBjYW4gc3RpbGwgdXNlIGl0IGluIGEgcGlwZToNCmBgYHtyIDE5LjYuMi00fQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQptdGNhcnMgJT4lIA0KICBzaG93X21pc3NpbmcoKSAlPiUgDQogIG11dGF0ZShtcGc9aWZlbHNlKG1wZzwyMCxOQSxtcGcpKSAlPiUgDQogIHNob3dfbWlzc2luZygpDQpgYGANCg0KIyMgMTkuNyBFbnZpcm9ubWVudA0KRGVmaW5lIGEgZnVuY3Rpb24gJ2YnIHRoYXQgdGFrZXMgYW4gYXJndW1lbnQgJ3gnIGFuZCByZXR1cm5zIHRoZSBzdW0gb2YgJ3gnIGFuZCAneScNCmBgYHtyIDE5LjctMX0NCmYgPC0gZnVuY3Rpb24oeCl7DQogIHgreQ0KfQ0KYGBgDQpEZW1vbnN0cmF0ZSBob3cgY2hhbmdpbmcgdGhlIHZhbHVlIG9mICd5JyBhZmZlY3RzIHRoZSByZXN1bHQgb2YgY2FsbGluZyBmdW5jdGlvbiAnZicNCmBgYHtyIDE5LjctMn0NCnkgPC0gMTAwDQpmKDEwKQ0KeSA8LSAxMDAwDQpmKDEwKQ0KYGBgDQpPdmVybG9hZCB0aGUgJysnIG9wZXJhdG9yIHRvIGJlaGF2ZSBkaWZmZXJlbnRseSBiYXNlZCBvbiBhIHJhbmRvbSBjb25kaXRpb24NCmBgYHtyIDE5LjctM30NCmArYCA8LSBmdW5jdGlvbih4LCB5KSB7DQogIGlmIChydW5pZigxKSA8IDAuMSkgew0KICAgIHN1bSh4LCB5KQ0KICB9IGVsc2Ugew0KICAgIHN1bSh4LCB5KSAqIDEuMQ0KICB9DQp9DQp0YWJsZShyZXBsaWNhdGUoMTAwMCwgMSArIDIpKQ0KIz4gDQojPiAgIDMgMy4zIA0KIz4gMTAwIDkwMA0Kcm0oYCtgKQ0KYGBgDQojIENoYXB0ZXIgMjA6IFZlY3RvcnMgDQoNCiMjIyAyMC4xLjEgUFJlcmVxdWlzaXRlcyANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQojIyAyMC4yIFZlY3RvciBiYXNpY3MgDQpEZXRlcm1pbmUgdGhlIGRhdGEgdHlwZSBvZiBkaWZmZXJlbnQgdmVjdG9ycw0KYGBge3J9DQp0eXBlb2YobGV0dGVycykNCnR5cGVvZigxOjEwKQ0KYGBgDQoNCkRldGVybWluZSB0aGUgbGVuZ3RoIG9mIGEgbGlzdCBhbmQgZGlzcGxheSBpdHMgY29udGVudHMNCmBgYHtyfQ0KeCA8LSBsaXN0KCJhIiwiYiIsMToxMCkNCmxlbmd0aCh4KQ0KeA0KYGBgDQpEZW1vbnN0cmF0ZSBtb2R1bG8gb3BlcmF0aW9uIGFuZCBjcmVhdGlvbiBvZiBsb2dpY2FsIHZlY3RvcnMNCmBgYHtyfQ0KMToxMCAlJSAzID09MA0KYyhULFQsRixOQSkNCmBgYA0KDQojIyMgMjAuMy4yIE51bWVyaWMNCkludGVnZXIgYW5kIGRvdWJsZSB2ZWN0b3JzIGFyZSBrbm93biBjb2xsZWN0aXZlbHkgYXMgbnVtZXJpYyB2ZWN0b3JzLiBJbiBSLCBudW1iZXJzIGFyZSBkb3VibGVzIGJ5IGRlZmF1bHQuIFRvIG1ha2UgYW4gaW50ZWdlciwgcGxhY2UgYW4gTCBhZnRlciB0aGUgbnVtYmVyOg0KYGBge3IgMjAuMy4yLTF9DQp0eXBlb2YoMSkNCnR5cGVvZigxTCkNCjEuNQ0KYGBgDQpEZW1vbnN0cmF0ZSB0aGUgYmVoYXZpb3Igb2YgZmxvYXRpbmcgcG9pbnQgYXJpdGhtZXRpYw0KYGBge3IgMjAuMy4yLTJ9DQp4IDwtIHNxcnQoMileMg0KeA0KeC0yDQoNCmBgYA0KRGVtb25zdHJhdGUgdGhlIGJlaGF2aW9yIG9mIGRpdmlzaW9uIGJ5IHplcm8NCmBgYHtyIDIwLjMuMi0zfQ0KYygtMSwwLDEpJS8lIDANCiMgWzFdIC1JbmYgIE5hTiAgSW5mDQpgYGANCg0KDQoNCiMjIyAyMC4zLjMgQ2hhcmFjdGVyDQpEZXRlcm1pbmUgdGhlIG1lbW9yeSBzaXplIG9mIGEgc3RyaW5nIGFuZCBhIHJlcGxpY2F0ZWQgc3RyaW5nIHZlY3Rvcg0KYGBge3IgMjAuMy4zfQ0KeCA8LSAiVGhpcyBpcyBhIHJlYXNvbmFibHkgbG9uZyBzdHJpbmcuIg0KcHJ5cjo6b2JqZWN0X3NpemUoeCkNCg0KeSA8LSByZXAoeCwxMDAwKQ0KcHJ5cjo6b2JqZWN0X3NpemUoeSkNCmBgYA0KDQoNCiMjIyAyMC4zLjQgTWlzc2luZyB2YWx1ZXMNCk5vdGUgdGhhdCBlYWNoIHR5cGUgb2YgYXRvbWljIHZlY3RvciBoYXMgaXRzIG93biBtaXNzaW5nIHZhbHVlOg0KYGBge3J9DQpOQSAgICAgICAgICAgICMgbG9naWNhbA0KDQpOQV9pbnRlZ2VyXyAgICMgaW50ZWdlcg0KDQpOQV9yZWFsXyAgICAgICMgZG91YmxlDQoNCk5BX2NoYXJhY3Rlcl8gIyBjaGFyYWN0ZXINCg0KYGBgDQpDYWxjdWxhdGUgdGhlIG51bWJlciBhbmQgcHJvcG9ydGlvbiBvZiBlbGVtZW50cyBpbiBhIHZlY3RvciBncmVhdGVyIHRoYW4gMTANCmBgYHtyIDIwLjQuMS0xfQ0KeCA8LSBzYW1wbGUoMjAsMTAwLHJlcGxhY2U9VCkNCnkgPC0geCA+IDEwDQpzdW0oeSkgIyBob3cgbWFueSBhcmUgZ3JlYXRlciB0aGFuIDEwPw0KbWVhbih5KSAjIHdoYXQgcHJvcG9ydGlvbiBhcmUgZ3JlYXRlciB0aGFuIDEwPw0KYGBgDQpgYGB7ciAyMC40LjEtMn0NCmlmIChsZW5ndGgoeCkpew0KfQ0KYGBgDQpEZXRlcm1pbmUgdGhlIGRhdGEgdHlwZSBvZiBkaWZmZXJlbnQgdmVjdG9ycw0KYGBge3IgMjAuNC4xLTN9DQp0eXBlb2YoYyhUUlVFLDFMKSkNCnR5cGVvZihjKDFMLDEuNSkpDQp0eXBlb2YoYygxLjUsImEiKSkNCmBgYA0KR2VuZXJhdGUgcmFuZG9tIG51bWVyaWMgb3IgbG9naWNhbCB2ZWN0b3JzDQpgYGB7cn0NCnNhbXBsZSgxMCkrMTAwDQpydW5pZigxMCk+MC41DQpgYGANCkRlbW9uc3RyYXRlIHZlY3RvciBhcml0aG1ldGljIHdpdGggdmVjdG9ycyBvZiBkaWZmZXJlbnQgbGVuZ3Rocw0KYGBge3J9DQoxOjEwICsxOjINCmBgYA0KDQpgYGB7cn0NCjE6MTArMTozDQpgYGANCkNyZWF0ZSBhIHRpYmJsZSB3aXRoIHR3byBjb2x1bW5zLCAneCcgYW5kICd5Jywgd2l0aCBkaWZmZXJlbnQgbGVuZ3Rocw0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KDQoNCnRpYmJsZSgNCiAgeD0xOjQsDQogIHk9cmVwKDE6MixlYWNoPTIpDQopDQpgYGANCg0KIyMjIyAyMC40LjQgTmFtaW5nIHZlY3RvcnMNCkFsbCB0eXBlcyBvZiB2ZWN0b3JzIGNhbiBiZSBuYW1lZC4gWW91IGNhbiBuYW1lIHRoZW0gZHVyaW5nIGNyZWF0aW4gd2l0aCBgYygpYDoNCmBgYHtyfQ0KYyh4PTEseT0yLHo9NCkNCmBgYA0KDQpPciBhZnRlciB0aGUgZmFjdCB3aXRoIGBwdXJyOjpzZXRfbmFtZXMoKWANCmBgYHtyfQ0Kc2V0X25hbWVzKDE6MyxjKCJhIiwiYiIsImMiKSkNCmBgYA0KDQpOYW1lZCB2ZWN0b3JzIGFyZSBtb3N0IHVzZWZ1bCBmb3Igc3Vic2V0dGluZywgZGVzY3JpYmVkIG5leHQuDQoNCiMjIyAyMC40LjUgU3Vic2V0dGluZw0KRGVtb25zdHJhdGUgc3Vic2V0dGluZyB2ZWN0b3JzIHdpdGggaW50ZWdlciBpbmRpY2VzDQpgYGB7cn0NCnggPC0gYygib25lIiwidHdvIiwidGhyZWUiLCJmb3VyIiwiZml2ZSIpDQp4W2MoMywyLDUpXQ0KYGBgDQoNCkJ5IHJlcGVhdGluZyBhIHBvc2l0aW9uLCB5b3UgY2FuIGFjdHVhbGx5IG1ha2UgYSBsb25nZXIgb3V0cHV0IHRoYW4gaW5wdXQ6DQpgYGB7cn0NCnhbYygxLDEsNSw1LDUsMildDQpgYGANCg0KTmVnYXRpdmUgdmFsdWVzIGRyb3AgdGhlIGVsZW1lbnRzIGF0IHRoZSBzcGVjaWZpZWQgcG9zaXRpb25zOg0KYGBge3J9DQp4W2MoLTEsLTMsLTUpXQ0KYGBgDQpUaGUgZXJyb3IgbWVzc2FnZSBtZW50aW9ucyBzdWJzZXR0aW5nIHdpdGggemVybywgd2hpY2ggcmV0dXJucyBubyB2YWx1ZXM6DQpgYGB7cn0NCnhbMF0NCmBgYA0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCnggPC0gYygxMCwzLE5BLDUsOCwxKQ0KDQojIHRpYmJsZSB0ZXN0DQp4IDwtIGFzLnRpYmJsZSh4LG5jb2w9MSkNCm5hbWVzKHgpPSJ2MSINCmlzLm5hKHgpDQp4ICU+JSBmaWx0ZXIodjEgPT0gTkEpDQoNCiMgYWxsIG5vbi1taXNzaW5nIHZhbHVlcyBvZiB4DQp4IDwtIGMoMTAsMyxOQSw1LDgsMSkNCnhbIWlzLm5hKHgpXQ0KDQojIGFsbCBldmVuIChvciBtaXNzaW5nKSB2YWx1ZXMgb2YgeA0KeFt4ICUlIDI9PTBdDQpgYGANCg0KMy4gSWYgeW91IGhhdmUgYSBuYW1lZCB2ZWN0b3IsIHlvdSBjYW4gc3Vic2V0IGl0IHdpdGggYSBjaGFyYWN0ZXIgdmVjdG9yOg0KYGBge3J9DQp4IDwtIGMoYWJjPTEsIGRlZj0yLHh5ej01KQ0KeFtjKCJ4eXoiLCJkZWYiKV0NCmBgYA0KDQojIyAyMC41IFJlY3Vyc2l2ZSB2ZWN0b3JzIChsaXN0cykNCkNyZWF0ZSBhIGxpc3Qgd2l0aCBudW1lcmljIGVsZW1lbnRzDQpgYGB7ciAyMC41LTF9DQp4IDwtIGxpc3QoMSwyLDMpDQp4DQpgYGANCkRpc3BsYXkgdGhlIHN0cnVjdHVyZSBvZiBsaXN0cyB3aXRoIGFuZCB3aXRob3V0IG5hbWVzDQpgYGB7ciAyMC41LTJ9DQpzdHIoeCkNCnhfbmFtZWQgPC0gbGlzdChhPTEsYj0yLGM9MykNCnN0cih4X25hbWVkKQ0KYGBgDQoNClVubGlrZSBhdG9taWMgdmVjdG9ycywgYGxpc3QoKWAgY2FuIGNvbnRhaW4gYSBtaXggb2Ygb2JqZWN0czoNCmBgYHtyIDIwLjUtM30NCnkgPC0gbGlzdCgiYSIsMUwsMS41LFQpDQpzdHIoeSkNCmBgYA0KDQpMaXN0IGNhbiBldmVuIGNvbnRhaW4gb3RoZXIgbGlzdHMhDQpgYGB7cn0NCnogPC0gbGlzdChsaXN0KDEsMiksbGlzdCgzLDQpKQ0Kc3RyKHopDQpgYGANCg0KIyMjIDIwLjUuMSBWaXN1YWxpemluZyBsaXN0cyANCmBgYHtyIDIwLjUuMX0NCngxIDwtIGxpc3QoYygxLDIpLGMoMyw0KSkNCngyIDwtIGxpc3QobGlzdCgxLDIpLGxpc3QoMyw0KSkNCngzIDwtIGxpc3QoMSxsaXN0KDIsbGlzdCgzKSkpDQp4MQ0KeDINCngzDQpgYGANCiMjIyAyMC41LjIgU3Vic2V0dGluZw0KQ3JlYXRlIGEgbGlzdCAnYScgd2l0aCBuYW1lZCBlbGVtZW50cyBhbmQgZGVtb25zdHJhdGUgc3Vic2V0dGluZw0KYGBge3J9DQphIDwtIGxpc3QoYSA9IDE6MywgYiA9ICJhIHN0cmluZyIsIGMgPSBwaSwgZCA9IGxpc3QoLTEsIC01KSkNCmBgYA0KDQpgYGB7ciAyMC41LjItMn0NCnN0cihhKQ0Kc3RyKGFbMToyXSkNCnN0cihhWzRdKQ0KYGBgDQpEZW1vbnN0cmF0ZSBzdWJzZXR0aW5nIGxpc3RzIHVzaW5nIGRvdWJsZSBzcXVhcmUgYnJhY2tldHMNCmBgYHtyIDIwLjUuMi0zfQ0Kc3RyKGFbWzFdXSkNCnN0cihhW1s0XV0pDQpgYGANCkFjY2VzcyBsaXN0IGVsZW1lbnRzIGJ5IG5hbWUgdXNpbmcgJCBvciBbWyBdXQ0KYGBge3J9DQphJGENCmFbWyJhIl1dDQpgYGANCiMjIDIwLjYgQXR0cmlidXRlcw0KRGVtb25zdHJhdGUgc2V0dGluZyBhbmQgcmV0cmlldmluZyBhdHRyaWJ1dGVzIG9mIHZlY3RvcnMNCmBgYHtyIDIwLjYtMX0NCnggPC0gMToxMA0KYXR0cih4LCJncmVldGluZyIpDQoNCmF0dHIoeCwiZ3JlZXRpbmciKSA8LSAiSGkhIg0KYXR0cih4LCJmYXJld2VsbCIpIDwtICJCeWUhIg0KYXR0cmlidXRlcyh4KQ0KYGBgDQpEZW1vbnN0cmF0ZSBtZXRob2RzIGZvciBjbGFzcyAnRGF0ZScNCmBgYHtyfQ0KYXMuRGF0ZQ0KYGBgDQpgYGB7ciAyMC42LTN9DQptZXRob2RzKCJhcy5EYXRlIikNCmBgYA0KUmV0cmlldmUgc3BlY2lmaWMgbWV0aG9kcyBmb3IgJ2FzLkRhdGUnDQpgYGB7cn0NCmdldFMzbWV0aG9kKCJhcy5EYXRlIiwiZGVmYXVsdCIpDQpnZXRTM21ldGhvZCgiYXMuRGF0ZSIsIm51bWVyaWMiKQ0KYGBgDQojIyMgMjAuNy4xIEZhY3RvcnMgDQpEZW1vbnN0cmF0ZSBjcmVhdGluZyBhIGZhY3RvciBhbmQgaW5zcGVjdGluZyBpdHMgYXR0cmlidXRlcw0KYGBge3J9DQp4IDwtIGZhY3RvcihjKCJhYiIsImNkIiwiYWIiKSxsZXZlbHM9YygiYWIiLCJjZCIsImVkIikpDQp0eXBlb2YoeCkNCmF0dHJpYnV0ZXMoeCkNCmBgYA0KDQojIyMgMjAuNy4yIERhdGVzIGFuZCBkYXRlLXRpbWVzDQpEYXRlcyBpbiBSIGFyZSBudW1lcmljIHZlY3RvcnMgdGhhdCByZXByZXNlbnQgdGhlIG51bWJlciBvZiBkYXlzIHNpbmNlIDEgSmFudWFyeSAxOTcwLg0KYGBge3IgMjAuNy4yLTF9DQp4IDwtIGFzLkRhdGUoIjE5NzEtMDEtMDEiKQ0KdW5jbGFzcyh4KQ0KDQp0eXBlb2YoeCkNCmF0dHJpYnV0ZXMoeCkNCmBgYA0KRGVtb25zdHJhdGUgY3JlYXRpbmcgYW5kIGluc3BlY3RpbmcgYSBkYXRlLXRpbWUgb2JqZWN0DQpgYGB7ciAyMC43LjItMn0NCnggPC0gbHVicmlkYXRlOjp5bWRfaG0oIjE5NzAtMDEtMDEgMDE6MDAiKQ0KdW5jbGFzcyh4KQ0KDQp0eXBlb2YoeCkNCmF0dHJpYnV0ZXMoeCkNCmBgYA0KRGVtb25zdHJhdGUgc2V0dGluZyBhbmQgcmV0cmlldmluZyB0aW1lIHpvbmUgZm9yIGRhdGUtdGltZSBvYmplY3QNCmBgYHtyIDIwLjcuMi0zfQ0KYXR0cih4LCJ0em9uZSIpIDwtICJVUy9QYWNpZmljIg0KeA0KDQphdHRyKHgsInR6b25lIikgPC0gIlVTL0Vhc3Rlcm4iDQp4DQpgYGANCg0KVGhlcmUgaXMgYW5vdGhlciB0eXBlIG9mIGRhdGUtdGltZXMgY2FsbGVkIFBPU0lYSXQuIFRoZXJlIGFyZSBidWlsdCBvbiB0b3Agb2YgbmFtZWQgbGlzdHM6DQpgYGB7cn0NCnkgPC0gYXMuUE9TSVhsdCh4KQ0KdHlwZW9mKHkpDQojPiBbMV0gImxpc3QiDQphdHRyaWJ1dGVzKHkpDQpgYGANCg0KIyMjIDIwLjcuMyBUaWJibGVzDQpUaWJibGVzIGFyZSBhdWdtZW50ZWQgbGlzdHM6IHRoZXkgaGF2ZSBjbGFzcyDigJx0YmxfZGbigJ0gKyDigJx0YmzigJ0gKyDigJxkYXRhLmZyYW1l4oCdLCBhbmQgYG5hbWVzYCAoY29sdW1uKSBhbmQgYHJvdy5uYW1lc2AgYXR0cmlidXRlczoNCmBgYHtyIDIwLjcuMy0xfQ0KdGIgPC0gdGliYmxlOjp0aWJibGUoeCA9IDE6NSwgeSA9IDU6MSkNCnR5cGVvZih0YikNCmF0dHJpYnV0ZXModGIpDQoNCmBgYA0KYGBge3IgMjAuNy4zLTJ9DQpkZiA8LSBkYXRhLmZyYW1lKHggPSAxOjUsIHkgPSA1OjEpDQp0eXBlb2YoZGYpDQphdHRyaWJ1dGVzKGRmKQ0KYGBgDQojIENoYXB0ZXIgMjE6IEl0ZXJhdGlvbg0KIyMjIDIxLjEuMSBQcmVyZXF1aXNpdGVzDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCiMjIDIxLjIgRm9yIGxvb3BzDQpJbWFnaW5lIHdlIGhhdmUgdGhpcyBzaW1wbGUgdGliYmxlOg0KYGBge3J9DQpkZiA8LSB0aWJibGUoDQogIGE9cm5vcm0oMTApLA0KICBiPXJub3JtKDEwKSwNCiAgYz1ybm9ybSgxMCksDQogIGQ9cm5vcm0oMTApDQopDQpgYGANCkNhbGN1bGF0ZSB0aGUgbWVkaWFuIGZvciBlYWNoIGNvbHVtbiBpbiBhIHRpYmJsZSANCmBgYHtyfQ0KbWVkaWFuKGRmJGEpDQptZWRpYW4oZGYkYikNCm1lZGlhbihkZiRjKQ0KbWVkaWFuKGRmJGQpDQpgYGANCkNhbGN1bGF0ZSB0aGUgbWVkaWFuIGZvciBlYWNoIGNvbHVtbiBpbiB0aGUgZGF0YSBmcmFtZSAnZGYnIHVzaW5nIGEgZm9yIGxvb3ANCmBgYHtyfQ0KZGYNCm91dHB1dCA8LSB2ZWN0b3IoImRvdWJsZSIsbmNvbChkZikpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKGRmKSl7DQogIG91dHB1dFtbaV1dIDwtIG1lZGlhbihkZltbaV1dKQ0KfQ0Kb3V0cHV0IDwtIHRpYmJsZShvdXRwdXQpDQpgYGANCkRlbW9uc3RyYXRlIHRoZSBiZWhhdmlvciBvZiBzZXFfYWxvbmcgYW5kIGxlbmd0aCBmdW5jdGlvbnMgd2l0aCBhbiBlbXB0eSB2ZWN0b3IgJ3knDQpgYGB7cn0NCnkgPC0gdmVjdG9yKCJkb3VibGUiLCAwKQ0Kc2VxX2Fsb25nKHkpDQojPiBpbnRlZ2VyKDApDQoxOmxlbmd0aCh5KQ0KIz4gWzFdIDEgMA0KYGBgDQojIyMgMjEuMy4xdiBNb2RpZnlpbmcgYW4gZXhpc3Rpbmcgb2JqZWN0DQpTb21ldGltZXMsIHlvdSB3YW50IHRvIHVzZSBhIGZvciBsb29wIHRvIG1vZGlmeSBhbiBleGlzdGluZyBvYmplY3QuIEZvciBleGFtcGxlLCByZW1lbWJlciBvdXIgY2hhbGxlbmdlcyBmcm9tIGZ1bmN0aW9ucy4gV2Ugd2FudGVkIHRvIHJlc2NhbGUgZXZlcnkgY29sdW1uIGluIGEgZGF0YSBmcmFtZToNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmRmIDwtIHRpYmJsZSgNCiAgYT1ybm9ybSgxMCksDQogIGI9cm5vcm0oMTApLA0KICBjPXJub3JtKDEwKSwNCiAgZD1ybm9ybSgxMCkNCikNCg0KcmVzY2FsZTAxIDwtIGZ1bmN0aW9uKHgpew0KICBybmcgPC0gcmFuZ2UoeCxuYS5ybT1UKQ0KICAoeC1ybmdbMV0pLyhybmdbMl0tcm5nWzFdKQ0KfQ0KDQpkZiRhIDwtIHJlc2NhbGUwMShkZiRhKQ0KZGYkYiA8LSByZXNjYWxlMDEoZGYkYikNCmRmJGMgPC0gcmVzY2FsZTAxKGRmJGMpDQpkZiRkIDwtIHJlc2NhbGUwMShkZiRkKQ0KDQpkZg0KYGBgDQoNCmBgYHtyfQ0KZm9yICggaSBpbiBzZXFfYWxvbmcoZGYpKXsNCiAgZGZbW2ldXSA8LSByZXNjYWxlMDEoZGZbW2ldXSkNCn0NCmBgYA0KDQojIyMgMjEuMy4yIExvb3BpbmcgcGF0dGVybnMNCmBgYHtyfQ0KeA0KcmVzdWx0cyA8LSB2ZWN0b3IoImxpc3QiLGxlbmd0aCh4KSkNCm5hbWVzKHJlc3VsdHMpIDwtIG5hbWVzKHgpDQpgYGANCg0KRGVtb25zdHJhdGUgbG9vcGluZyBwYXR0ZXJucyB1c2luZyBhIGZvciBsb29wIHRvIGl0ZXJhdGUgb3ZlciBhIGxpc3QgJ3gnIGFuZCBzdG9yZSByZXN1bHRzIGluIGEgbGlzdCAncmVzdWx0cycNCmBgYHtyfQ0KZm9yKGkgaW4gc2VxX2Fsb25nKHgpKXsNCiAgbmFtZSA8LSBuYW1lcyh4KVtbaV1dDQogIHZhbHVlIDwtIHhbW2ldXQ0KfQ0KYGBgDQoNCiMjIyAyMS4zLjMgVW5rbm93biBvdXRwdXQgbGVuZ3RoDQpDcmVhdGUgYSB2ZWN0b3IgJ291dHB1dCcgd2l0aCB1bmtub3duIGxlbmd0aCBhbmQgc3RvcmUgcmVzdWx0cyBmcm9tIGEgZm9yIGxvb3AgaW4gaXQNCmBgYHtyfQ0KbWVhbnMgPC0gYygwLDEsMikNCg0Kb3V0cHV0IDwtIGRvdWJsZSgpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKG1lYW5zKSl7DQogIG4gPC0gc2FtcGxlKDEwMCwxKQ0KICBvdXRwdXQgPC0gYyhvdXRwdXQscm5vcm0obixtZWFuc1tbaV1dKSkNCn0NCnN0cihvdXRwdXQpDQpvdXRwdXQNCmBgYA0KDQpDcmVhdGUgYSBsaXN0ICdvdXQnIHdpdGggdW5rbm93biBsZW5ndGggYW5kIHN0b3JlIHJlc3VsdHMgZnJvbSBhIGZvciBsb29wIGluIGl0DQpgYGB7cn0NCm91dCA8LSB2ZWN0b3IoImxpc3QiLGxlbmd0aChtZWFucykpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKG1lYW5zKSl7DQogIG4gPC0gc2FtcGxlKDEwMCwxKQ0KICBvdXRbW2ldXSA8LSBybm9ybShuLG1lYW5zW1tpXV0pDQp9DQpzdHIob3V0KQ0Kc3RyKHVubGlzdChvdXQpKQ0KYGBgDQojIyMgMjEuMy40IFVua25vd24gc2VxdWVuY2UgbGVuZ3RoDQoNCg0KQSB3aGlsZSBsb29wIGlzIGFsc28gbW9yZSBnZW5lcmFsIHRoYW4gYSBmb3IgbG9vcCwgYmVjYXVzZSB5b3UgY2FuIHJld3JpdGUgYW55IGZvciBsb29wIGFzIGEgd2hpbGUgbG9vcCwgYnV0IHlvdSBjYW4ndCByZXdyaXRlIGV2ZXJ5IHdoaWxlIGxvb3AgYXMgZm9yIGxvb3A6DQpgYGB7cn0NCmZvciAoaSBpbiBzZXFfYWxvbmcoeCkpIHsNCiAgIyBib2R5DQp9DQoNCiMgRXF1aXZhbGVudCB0bw0KaSA8LSAxDQp3aGlsZSAoaSA8PSBsZW5ndGgoeCkpIHsNCiAgIyBib2R5DQogIGkgPC0gaSArIDEgDQp9DQoNCmBgYA0KDQpIZXJob3cgd2UgY291bGQgdXNlIGEgd2hpbGUgbG9vcCB0byBmaW5kIGhvdyBtYW55IHRyaWVzIGl0IHRha2VzIHRvIGdldCB0aHJlZSBoZWFkcyBpbiBhIHJvdzogDQpgYGB7cn0NCmZsaXAgPC0gZnVuY3Rpb24oKSBzYW1wbGUoYygiVCIsICJIIiksIDEpDQoNCmZsaXBzIDwtIDANCm5oZWFkcyA8LSAwDQoNCndoaWxlIChuaGVhZHMgPCAzKSB7DQogIGlmIChmbGlwKCkgPT0gIkgiKSB7DQogICAgbmhlYWRzIDwtIG5oZWFkcyArIDENCiAgfSBlbHNlIHsNCiAgICBuaGVhZHMgPC0gMA0KICB9DQogIGZsaXBzIDwtIGZsaXBzICsgMQ0KfQ0KZmxpcHMNCmBgYA0KIyMgMjEuNCBGb3IgbG9vcHMgdnMuIGZ1bmN0aW9uYWxzDQpDb21wYXJlIGZvciBsb29wIGFuZCBmdW5jdGlvbmFsIGFwcHJvYWNoZXMgZm9yIGNhbGN1bGF0aW5nIGNvbHVtbiBtZWFucyBpbiBhIGRhdGEgZnJhbWUNCmBgYHtyfQ0KZGYgPC0gdGliYmxlKA0KICBhPXJub3JtKDEwKSwNCiAgYj1ybm9ybSgxMCksDQogIGM9cm5vcm0oMTApLA0KICBkPXJub3JtKDEwKQ0KKQ0KYGBgDQpVc2luZyBmb3IgbG9vcA0KYGBge3J9DQpvdXRwdXQgPC0gdmVjdG9yKCJkb3VibGUiLGxlbmd0aChkZikpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKGRmKSl7DQogIG91dHB1dFtbaV1dIDwtIG1lYW4oZGZbW2ldXSkNCn0NCm91dHB1dA0KYGBgDQpVc2luZyBmdW5jdGlvbmFsIGFwcHJvYWNoIHdpdGggYSBjdXN0b20gZnVuY3Rpb24gJ2NvbF9tZWFuJw0KYGBge3J9DQpjb2xfbWVhbiA8LSBmdW5jdGlvbihkZil7DQogIG91dHB1dCA8LSB2ZWN0b3IoImRvdWJsZSIsbGVuZ3RoKGRmKSkNCiAgZm9yIChpIGluIHNlcV9hbG9uZyhkZikpew0KICAgIG91dHB1dFtpXSA8LSBtZWFuKGRmW1tpXV0pDQogIH0NCiAgb3V0cHV0DQp9DQpgYGANCkRlZmluZSBhIGZ1bmN0aW9uICdjb2xfbWVkaWFuJyB0byBjYWxjdWxhdGUgdGhlIG1lZGlhbiBmb3IgZWFjaCBjb2x1bW4gaW4gdGhlIGRhdGEgZnJhbWUgJ2RmJw0KYGBge3J9DQpjb2xfbWVkaWFuIDwtIGZ1bmN0aW9uKGRmKXsNCiAgb3V0cHV0IDwtIHZlY3RvcigiZG91YmxlIixoaChkZikpDQogIGZvciAoaSBpbiBzZXFfYWxvbmcoZGYpKXsNCiAgICBvdXRwdXRbaV0gPC0gbWVkaWFuKGRmW1tpXV0pDQogIH0NCiAgb3V0cHV0DQp9DQoNCmNvbF9zZCA8LSBmdW5jdGlvbihkZil7DQogIG91dHB1dCA8LSB2ZWN0b3IoImRvdWJsZSIsbGVuZ3RoKGRmKSkNCiAgZm9yIChpIGluIHNlcV9hbG9uZyhkZikpew0KICAgIG91dHB1dFtpXSA8LSBzZChkZltbaV1dKQ0KICB9DQogIG91dHB1dA0KfQ0KDQpkZg0KYGBgDQpEZWZpbmUgZnVuY3Rpb25zIGYxLCBmMiwgYW5kIGYzIGZvciBjYWxjdWxhdGluZyBkaWZmZXJlbnQgcG93ZXJzIG9mIGFic29sdXRlIGRldmlhdGlvbiBmcm9tIHRoZSBtZWFuDQpgYGB7cn0NCmYxIDwtIGZ1bmN0aW9uKHgpIGFicyh4LW1lYW4oeCkpXjENCmYyIDwtIGZ1bmN0aW9uKHgpIGFicyh4LW1lYW4oeCkpXjINCmYzIDwtIGZ1bmN0aW9uKHgpIGFicyh4LW1lYW4oeCkpXjMNCmBgYA0KRGVmaW5lIGEgZnVuY3Rpb24gJ2YnIHRvIGNhbGN1bGF0ZSB0aGUgYWJzb2x1dGUgZGV2aWF0aW9uIGZyb20gdGhlIG1lYW4gcmFpc2VkIHRvIGEgZ2l2ZW4gcG93ZXIgJ2knDQpgYGB7cn0NCmYgPC0gZnVuY3Rpb24oeCxpKSBhYnMoeC1tZWFuKHgpKV5pDQpgYGANCkRlZmluZSBhIGZ1bmN0aW9uICdjb2xfc3VtbWFyeScgdG8gYXBwbHkgYSBzdW1tYXJ5IGZ1bmN0aW9uICdmdW4nIHRvIGVhY2ggY29sdW1uIG9mIHRoZSBkYXRhIGZyYW1lICdkZicNCmBgYHtyfQ0KY29sX3N1bW1hcnkgPC0gZnVuY3Rpb24oZGYsIGZ1bikgew0KICBvdXQgPC0gdmVjdG9yKCJkb3VibGUiLCBsZW5ndGgoZGYpKQ0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKGRmKSkgew0KICAgIG91dFtpXSA8LSBmdW4oZGZbW2ldXSkNCiAgfQ0KICBvdXQNCn0NCmNvbF9zdW1tYXJ5KGRmLCBtZWRpYW4pDQpjb2xfc3VtbWFyeShkZiwgbWVhbikNCmBgYA0KRGVtb25zdHJhdGUgdGhlIHVzZSBvZiAnbWFwX2RibCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlIHRvIGFwcGx5IGEgZnVuY3Rpb24gdG8gZWFjaCBjb2x1bW4gb2YgdGhlIGRhdGEgZnJhbWUgJ2RmJw0KYGBge3J9DQpsaWJyYXJ5KHB1cnJyKQ0KaGVhZChkZikNCg0KDQojIFJlZmVyZW5jZSAtIGZvciBsb29wKCkNCm91dHB1dCA8LSB2ZWN0b3IoImRvdWJsZSIsbGVuZ3RoKGRmKSkNCmZvciAoaSBpbiBzZXFfYWxvbmcoZGYpKXsNCiAgb3V0cHV0W1tpXV0gPC0gbWVhbihkZltbaV1dKQ0KfQ0Kb3V0cHV0DQoNCm1hcF9kYmwoZGYsbWVhbikNCm1hcF9kYmwoZGYsbWVkaWFuKQ0KbWFwX2RibChkZixzZCkNCmBgYA0KDQpgYGB7cn0NCmRmICU+JSBtYXBfZGJsKG1lYW4pDQpkZiAlPiUgbWFwX2RibChtZWRpYW4pDQpkZiAlPiUgbWFwX2RibChzZCkNCmBgYA0KRGVtb25zdHJhdGUgdGhlIHVzZSBvZiAnbWFwX2RibCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlIHdpdGggYWRkaXRpb25hbCBhcmd1bWVudHMNCmBgYHtyfQ0KbWFwX2RibChkZixtZWFuLHRyaW09MC41KQ0KYGBgDQpEZW1vbnN0cmF0ZSB0aGUgdXNlIG9mICdtYXBfaW50JyBmcm9tIHRoZSAncHVycnInIHBhY2thZ2UgdG8gYXBwbHkgYSBmdW5jdGlvbiB0aGF0IHJldHVybnMgaW50ZWdlcnMgdG8gZWFjaCBlbGVtZW50IG9mIGEgbGlzdA0KYGBge3J9DQp6IDwtIGxpc3QoeD0xOjMseT00OjUpDQp6DQoNCm1hcF9pbnQoeixsZW5ndGgpDQpgYGANCg0KIyMjIDIxLjUuMSBTaG9ydGN1dHMgDQpEZW1vbnN0cmF0ZSB0aGUgdXNlIG9mICdzYWZlbHknIGZyb20gdGhlICdwdXJycicgcGFja2FnZSB0byBjcmVhdGUgYSBzYWZlIHZlcnNpb24gb2YgYSBmdW5jdGlvbg0KDQpgYGB7cn0NCnNhZmVfbG9nIDwtIHNhZmVseShsb2cpDQpzdHIoc2FmZV9sb2coMTApKQ0Kc3RyKHNhZmVfbG9nKCJhIikpDQpgYGANCkRlbW9uc3RyYXRlIHRoZSB1c2Ugb2YgJ21hcCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlIHdpdGggJ3NhZmVseScgdG8gYXBwbHkgYSBzYWZlIHZlcnNpb24gb2YgYSBmdW5jdGlvbiB0byBlYWNoIGVsZW1lbnQgb2YgYSBsaXN0DQoNCmBgYHtyfQ0KeCA8LSBsaXN0KDEsMTAsImEiKQ0KeSA8LSB4ICU+JSBtYXAoc2FmZWx5KGxvZykpDQpzdHIoeSkNCg0KYGBgDQpEZW1vbnN0cmF0ZSB0aGUgdXNlIG9mICd0cmFuc3Bvc2UnIGZyb20gdGhlICdwdXJycicgcGFja2FnZSB0byB0cmFuc3Bvc2UgYSBsaXN0IG9mIGxpc3RzDQpgYGB7cn0NCnkgPC0geCAlPiUgdHJhbnNwb3NlKCkNCnN0cih5KQ0KYGBgDQpEZW1vbnN0cmF0ZSB0aGUgdXNlIG9mIGVycm9yIGhhbmRsaW5nIHdpdGggJ21hcF9sZ2wnIGFuZCAnaXNfbnVsbCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlDQpgYGB7cn0NCmlzX29rIDwtIHkkZXJyb3IgJT4lIG1hcF9sZ2woaXNfbnVsbCkNCnhbIWlzX29rXQ0KIyB5JHJlc3VsdFtpc19va10gJT4lIGZsYXR0ZW5fZGJsKCkNCmBgYA0KDQpQdXJyciBwcm92aWRlcyB0d28gb3RoZXIgdXNlZnVsIGFkdmVyYnM6DQpgYGB7cn0NCnggPC0gbGlzdCgxLDEwLCJhIikNCnggJT4lIG1hcF9kYmwocG9zc2libHkobG9nLE5BX3JlYWxfKSkNCmBgYA0KRGVtb25zdHJhdGUgdGhlIHVzZSBvZiAncXVpZXRseScgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlIHRvIHN1cHByZXNzIGVycm9ycyBhbmQgcmV0dXJuIHJlc3VsdHMgd2l0aCB3YXJuaW5ncw0KYGBge3J9DQp4IDwtIGxpc3QoMSwtMSkNCnggJT4lIG1hcChxdWlldGx5KGxvZykpICU+JSBzdHIoKQ0KYGBgDQoNCg0KIyMgMjEuNyBNYXBwaW5nIG92ZXIgbXVsdGlwbGUgYXJndW1lbnRzIA0KR2VuZXJhdGUgcmFuZG9tIG51bWJlcnMgZnJvbSBub3JtYWwgZGlzdHJpYnV0aW9ucyB3aXRoIGRpZmZlcmVudCBtZWFucyB1c2luZyAnbWFwJyBmcm9tIHRoZSAncHVycnInIHBhY2thZ2UNCmBgYHtyfQ0KbXUgPC0gbGlzdCg1LDEwLC0zKQ0KbXUgJT4lIA0KICBtYXAocm5vcm0sbj01KSAlPiUgDQogIHN0cigpDQpgYGANCkdlbmVyYXRlIHJhbmRvbSBudW1iZXJzIGZyb20gbm9ybWFsIGRpc3RyaWJ1dGlvbnMgd2l0aCBkaWZmZXJlbnQgbWVhbnMgYW5kIHN0YW5kYXJkIGRldmlhdGlvbnMgdXNpbmcgJ21hcDInIGZyb20gdGhlICdwdXJycicgcGFja2FnZQ0KYGBge3J9DQpzaWdtYSA8LSBsaXN0KDEsNSwxMCkNCnNlcV9hbG9uZyhtdSkgJT4lIA0KICBtYXAofnJub3JtKDUsbXVbWy5dXSxzaWdtYVtbLl1dKSkgJT4lIA0KICBzdHIoKQ0KYGBgDQpEZWZpbmUgYSBjdXN0b20gJ21hcDInIGZ1bmN0aW9uIHRvIGFwcGx5IGEgYmluYXJ5IGZ1bmN0aW9uIHRvIGNvcnJlc3BvbmRpbmcgZWxlbWVudHMgb2YgdHdvIGxpc3RzDQpgYGB7cn0NCm1hcDIobXUsc2lnbWEscm5vcm0sbj01KSAlPiUgc3RyKCkNCmBgYA0KDQpgYGB7cn0NCm1hcDIgPC0gZnVuY3Rpb24oeCx5LGYsLi4uKXsNCiAgb3V0IDwtIHZlY3RvcigibGlzdCIsbGVuZ3RoKHgpKQ0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHgpKXsNCiAgICBvdXRbW2ldXSA8LSBmKHhbW2ldXSx5W1tpXV0sLi4uKQ0KICB9DQogIG91dA0KfQ0KYGBgDQpBcHBseSBhIGZ1bmN0aW9uIHRvIGNvcnJlc3BvbmRpbmcgZWxlbWVudHMgb2YgbXVsdGlwbGUgbGlzdHMgdXNpbmcgJ3BtYXAnIGZyb20gdGhlICdwdXJycicgcGFja2FnZQ0KYGBge3J9DQoNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KHB1cnJyKQ0KDQpuIDwtIGxpc3QoMSwzLDUpDQphcmdzMSA8LSBsaXN0KG4sbXUsc2lnbWEpDQphcmdzMSAlPiUgDQogIHBtYXAocm5vcm0pICU+JSANCiAgc3RyKCkNCmBgYA0KQXBwbHkgYSBmdW5jdGlvbiB0byBjb3JyZXNwb25kaW5nIGVsZW1lbnRzIG9mIG11bHRpcGxlIGxpc3RzIHdpdGggbmFtZWQgcGFyYW1ldGVycyB1c2luZyAncG1hcCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlDQpgYGB7cn0NCmFyZ3MyIDwtIGxpc3QobWVhbj1tdSwgc2Q9c2lnbWEsbj1uKQ0KYXJnczIgJT4lIA0KICBwbWFwKHJub3JtKSAlPiUgDQogIHN0cigpDQpgYGANCkFwcGx5IGEgZnVuY3Rpb24gdG8gY29ycmVzcG9uZGluZyByb3dzIG9mIGEgZGF0YSBmcmFtZSB1c2luZyAncG1hcCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlIHdpdGggYSB0aWJibGUNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpwYXJtcyA8LSB0cmliYmxlKA0KICB+bWVhbix+c2Qsfm4sDQogIDUsMSwxLA0KICAxMCw1LDMsDQogIC0zLDEwLDUNCikNCg0KcGFybXMgJT4lIA0KICBwbWFwKHJub3JtKQ0KYGBgDQojIyMgMjEuNy4xIEludm9saW5nIGRpZmZlcmVudCBmdW5jdGlvbnMNCkludm9rZSBkaWZmZXJlbnQgZnVuY3Rpb25zIHdpdGggZGlmZmVyZW50IHBhcmFtZXRlcnMgdXNpbmcgJ2ludm9rZV9tYXAnIGZyb20gdGhlICdwdXJycicgcGFja2FnZQ0KYGBge3J9DQpmIDwtIGMoInJ1bmlmIiwicm5vcm0iLCJycG9pcyIpDQpwYXJhbSA8LSBsaXN0KA0KICBsaXN0KG1pbj0tMSxtYXg9MSksDQogIGxpc3Qoc2Q9NSksDQogIGxpc3QobGFtYmRhPTEwKQ0KKQ0KDQpmDQpwYXJhbQ0KYGBgDQoNClRvIGhhbmRsZSB0aGlzIGNhc2UsIHlvdSBjYW4gdXNlIGBpbnZva2VfbWFwKClgOg0KYGBge3J9DQppbnZva2VfbWFwKGYscGFyYW0sbj01KSAlPiUgDQogIHN0cigpDQpgYGANCkludm9rZSBkaWZmZXJlbnQgZnVuY3Rpb25zIHdpdGggZGlmZmVyZW50IHBhcmFtZXRlcnMgdXNpbmcgJ3BtYXAnIGZyb20gdGhlICdwdXJycicgcGFja2FnZSBhbmQgYSB0aWJibGUNCmBgYHtyfQ0Kc2ltIDwtIHRyaWJibGUoDQogIH5mLCAgICAgIH5wYXJhbXMsDQogICJydW5pZiIsIGxpc3QobWluID0gLTEsIG1heCA9IDEpLA0KICAicm5vcm0iLCBsaXN0KHNkID0gNSksDQogICJycG9pcyIsIGxpc3QobGFtYmRhID0gMTApDQopDQpzaW0gJT4lIA0KICBtdXRhdGUoc2ltID0gaW52b2tlX21hcChmLCBwYXJhbXMsIG4gPSAxMCkpDQpgYGANCg0KIyMgMjEuOCBXYWxrDQpQZXJmb3JtIHNpZGUgZWZmZWN0cyB3aXRob3V0IHJldHVybmluZyBhIHZhbHVlIGZvciBlYWNoIGVsZW1lbnQgb2YgYSBsaXN0IHVzaW5nICd3YWxrJyBmcm9tIHRoZSAncHVycnInIHBhY2thZ2UNCmBgYHtyfQ0KeCA8LSBsaXN0KDEsImEiLDMpDQp4ICU+JSANCiAgd2FsayhwcmludCkNCmBgYA0KUGVyZm9ybSBzaWRlIGVmZmVjdHMgb24gZWFjaCBlbGVtZW50IG9mIGEgbGlzdCB1c2luZyAnd2FsaycgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlLCB0aGVuIHNhdmUgdGhlIHJlc3VsdHMNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KcGxvdHMgPC0gbXRjYXJzICU+JSANCiAgc3BsaXQoLiRjeWwpICU+JSANCiAgbWFwKH5nZ3Bsb3QoLiwgYWVzKG1wZywgd3QpKSArIGdlb21fcG9pbnQoKSkNCnBhdGhzIDwtIHN0cmluZ3I6OnN0cl9jKG5hbWVzKHBsb3RzKSwgIi5wZGYiKQ0KDQpwd2FsayhsaXN0KHBhdGhzLCBwbG90cyksIGdnc2F2ZSwgcGF0aCA9IHRlbXBkaXIoKSkNCmBgYA0KUmV0YWluIG9yIHJlbW92ZSBlbGVtZW50cyBvZiBhIGxpc3QgYmFzZWQgb24gYSBwcmVkaWNhdGUgZnVuY3Rpb24gdXNpbmcgJ2tlZXAnIGFuZCAnZGlzY2FyZCcgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlDQpgYGB7cn0NCmlyaXMgJT4lIA0KICBrZWVwKGlzLmZhY3RvcikgJT4lIA0KICBzdHIoKQ0KDQppcmlzICU+JSANCiAgZGlzY2FyZChpcy5mYWN0b3IpICU+JQ0KICBzdHIoKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtYWdyaXR0cikNCg0KDQpgYGANCg0KIyMjIDIxLjkuMiBSZWR1Y2UgYW5kIGFjY3VtdWxhdGUNCkl0ZXJhdGl2ZWx5IGNvbWJpbmUgZWxlbWVudHMgb2YgYSBsaXN0IHVzaW5nIGEgYmluYXJ5IGZ1bmN0aW9uIHdpdGggJ3JlZHVjZScgZnJvbSB0aGUgJ3B1cnJyJyBwYWNrYWdlDQpgYGB7cn0NCmRmcyA8LSBsaXN0KA0KICBhZ2U9dGliYmxlKG5hbWU9IkpvaG4iLGFnZT0zMCksDQogIHNleD10aWJibGUobmFtZT1jKCJKb2huIiwiTWFyeSIpLHNleD1jKCJNIiwiRiIpKSwNCiAgdHJ0PXRpYmJsZShuYW1lPSJNYXJ5Iix0cmVhdG1lbnQ9IkEiKQ0KKQ0KDQpkZnMgJT4lIHJlZHVjZShmdWxsX2pvaW4pDQpgYGANCkZpbmQgdGhlIGludGVyc2VjdGlvbiBvZiBtdWx0aXBsZSB2ZWN0b3JzIHVzaW5nICdyZWR1Y2UnIGZyb20gdGhlICdwdXJycicgcGFja2FnZQ0KYGBge3J9DQp2cyA8LSBsaXN0KA0KICBjKDEsMyw1LDYsMTApLA0KICBjKDEsMiwzLDcsOCwxMCksDQogIGMoMSwyLDMsNCw4LDksMTApDQopDQp2cyAlPiUgcmVkdWNlKGludGVyc2VjdCkNCmBgYA0KSXRlcmF0aXZlbHkgYXBwbHkgYSBmdW5jdGlvbiB0byBlbGVtZW50cyBvZiBhIGxpc3QgdXNpbmcgJ2FjY3VtdWxhdGUnIGZyb20gdGhlICdwdXJycicgcGFja2FnZQ0KYGBge3J9DQp4IDwtIHNhbXBsZSgxMCkNCngNCnggJT4lIGFjY3VtdWxhdGUoYCtgKQ0KYGBgDQoNCg==